diff --git a/pom.xml b/pom.xml index 5047cd7ca..c4be8f5b6 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ 2.0.9 3.12.8 - 1.18-R0.1-SNAPSHOT + 1.18.2-R0.1-SNAPSHOT 1.16.5-R0.1-SNAPSHOT diff --git a/src/main/java/world/bentobox/bentobox/nms/NMSAbstraction.java b/src/main/java/world/bentobox/bentobox/nms/NMSAbstraction.java deleted file mode 100644 index 40e0f4e97..000000000 --- a/src/main/java/world/bentobox/bentobox/nms/NMSAbstraction.java +++ /dev/null @@ -1,49 +0,0 @@ -package world.bentobox.bentobox.nms; - -import org.bukkit.Chunk; -import org.bukkit.block.data.BlockData; -import org.bukkit.generator.ChunkGenerator; -import org.bukkit.util.BoundingBox; - -public interface NMSAbstraction { - /** - * Copy the chunk data and biome grid to the given chunk. - * @param chunk - chunk to copy to - * @param chunkData - chunk data to copy - * @param biomeGrid - biome grid to copy to - * @param limitBox - bounding box to limit the copying - */ - default void copyChunkDataToChunk(Chunk chunk, ChunkGenerator.ChunkData chunkData, ChunkGenerator.BiomeGrid biomeGrid, BoundingBox limitBox) { - double baseX = chunk.getX() << 4; - double baseZ = chunk.getZ() << 4; - int minHeight = chunk.getWorld().getMinHeight(); - int maxHeight = chunk.getWorld().getMaxHeight(); - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - if (!limitBox.contains(baseX + x, 0, baseZ + z)) { - continue; - } - for (int y = minHeight; y < maxHeight; y++) { - setBlockInNativeChunk(chunk, x, y, z, chunkData.getBlockData(x, y, z), false); - // 3D biomes, 4 blocks separated - if (x % 4 == 0 && y % 4 == 0 && z % 4 == 0) { - chunk.getBlock(x, y, z).setBiome(biomeGrid.getBiome(x, y, z)); - } - } - } - } - } - - /** - * Update the low-level chunk information for the given block to the new block ID and data. This - * change will not be propagated to clients until the chunk is refreshed to them. - * @param chunk - chunk to be changed - * @param x - x coordinate within chunk 0 - 15 - * @param y - y coordinate within chunk 0 - world height, e.g. 255 - * @param z - z coordinate within chunk 0 - 15 - * @param blockData - block data to set the block - * @param applyPhysics - apply physics or not - */ - void setBlockInNativeChunk(Chunk chunk, int x, int y, int z, BlockData blockData, boolean applyPhysics); - -} diff --git a/src/main/java/world/bentobox/bentobox/nms/SimpleWorldRegenerator.java b/src/main/java/world/bentobox/bentobox/nms/SimpleWorldRegenerator.java new file mode 100644 index 000000000..86075a382 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/nms/SimpleWorldRegenerator.java @@ -0,0 +1,137 @@ +package world.bentobox.bentobox.nms; + +import io.papermc.lib.PaperLib; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.util.BoundingBox; +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.database.objects.IslandDeletion; +import world.bentobox.bentobox.util.MyBiomeGrid; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CompletableFuture; + +public abstract class SimpleWorldRegenerator implements WorldRegenerator { + private final BentoBox plugin; + + protected SimpleWorldRegenerator() { + this.plugin = BentoBox.getInstance(); + } + + /** + * Update the low-level chunk information for the given block to the new block ID and data. This + * change will not be propagated to clients until the chunk is refreshed to them. + * + * @param chunk - chunk to be changed + * @param x - x coordinate within chunk 0 - 15 + * @param y - y coordinate within chunk 0 - world height, e.g. 255 + * @param z - z coordinate within chunk 0 - 15 + * @param blockData - block data to set the block + * @param applyPhysics - apply physics or not + */ + protected abstract void setBlockInNativeChunk(Chunk chunk, int x, int y, int z, BlockData blockData, boolean applyPhysics); + + @Override + public CompletableFuture regenerate(GameModeAddon gm, IslandDeletion di, World world) { + CompletableFuture bigFuture = new CompletableFuture<>(); + new BukkitRunnable() { + private int chunkX = di.getMinXChunk(); + private int chunkZ = di.getMinZChunk(); + CompletableFuture currentTask = CompletableFuture.completedFuture(null); + + @Override + public void run() { + if (!currentTask.isDone()) return; + if (isEnded(chunkX)) { + cancel(); + bigFuture.complete(null); + return; + } + List> newTasks = new ArrayList<>(); + for (int i = 0; i < plugin.getSettings().getDeleteSpeed(); i++) { + if (isEnded(chunkX)) { + break; + } + final int x = chunkX; + final int z = chunkZ; + newTasks.add(regenerateChunk(gm, di, world, x, z)); + chunkZ++; + if (chunkZ > di.getMaxZChunk()) { + chunkZ = di.getMinZChunk(); + chunkX++; + } + } + currentTask = CompletableFuture.allOf(newTasks.toArray(new CompletableFuture[0])); + } + + private boolean isEnded(int chunkX) { + return chunkX > di.getMaxXChunk(); + } + }.runTaskTimer(plugin, 0L, 20L); + return bigFuture; + } + + @SuppressWarnings("deprecation") + private CompletableFuture regenerateChunk(GameModeAddon gm, IslandDeletion di, World world, int chunkX, int chunkZ) { + CompletableFuture chunkFuture = PaperLib.getChunkAtAsync(world, chunkX, chunkZ); + CompletableFuture invFuture = chunkFuture.thenAccept(chunk -> + Arrays.stream(chunk.getTileEntities()).filter(InventoryHolder.class::isInstance) + .filter(te -> di.inBounds(te.getLocation().getBlockX(), te.getLocation().getBlockZ())) + .forEach(te -> ((InventoryHolder) te).getInventory().clear()) + ); + CompletableFuture entitiesFuture = chunkFuture.thenAccept(chunk -> { + for (Entity e : chunk.getEntities()) { + if (!(e instanceof Player)) { + e.remove(); + } + } + }); + CompletableFuture copyFuture = chunkFuture.thenApply(chunk -> { + // Reset blocks + MyBiomeGrid grid = new MyBiomeGrid(chunk.getWorld().getEnvironment()); + ChunkGenerator cg = gm.getDefaultWorldGenerator(chunk.getWorld().getName(), "delete"); + // Will be null if use-own-generator is set to true + if (cg != null) { + ChunkGenerator.ChunkData cd = cg.generateChunkData(chunk.getWorld(), new Random(), chunk.getX(), chunk.getZ(), grid); + copyChunkDataToChunk(chunk, cd, grid, di.getBox()); + } + return chunk; + }); + CompletableFuture postCopyFuture = copyFuture.thenAccept(chunk -> { + // Remove all entities in chunk, including any dropped items as a result of clearing the blocks above + Arrays.stream(chunk.getEntities()).filter(e -> !(e instanceof Player) && di.inBounds(e.getLocation().getBlockX(), e.getLocation().getBlockZ())).forEach(Entity::remove); + }); + return CompletableFuture.allOf(invFuture, entitiesFuture, postCopyFuture); + } + + private void copyChunkDataToChunk(Chunk chunk, ChunkGenerator.ChunkData chunkData, ChunkGenerator.BiomeGrid biomeGrid, BoundingBox limitBox) { + double baseX = chunk.getX() << 4; + double baseZ = chunk.getZ() << 4; + int minHeight = chunk.getWorld().getMinHeight(); + int maxHeight = chunk.getWorld().getMaxHeight(); + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + if (!limitBox.contains(baseX + x, 0, baseZ + z)) { + continue; + } + for (int y = minHeight; y < maxHeight; y++) { + setBlockInNativeChunk(chunk, x, y, z, chunkData.getBlockData(x, y, z), false); + // 3D biomes, 4 blocks separated + if (x % 4 == 0 && y % 4 == 0 && z % 4 == 0) { + chunk.getBlock(x, y, z).setBiome(biomeGrid.getBiome(x, y, z)); + } + } + } + } + } +} diff --git a/src/main/java/world/bentobox/bentobox/nms/WorldRegenerator.java b/src/main/java/world/bentobox/bentobox/nms/WorldRegenerator.java new file mode 100644 index 000000000..a0bafb545 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/nms/WorldRegenerator.java @@ -0,0 +1,22 @@ +package world.bentobox.bentobox.nms; + +import org.bukkit.World; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.database.objects.IslandDeletion; + +import java.util.concurrent.CompletableFuture; + +/** + * A world generator used by {@link world.bentobox.bentobox.util.DeleteIslandChunks} + */ +public interface WorldRegenerator { + /** + * Create a future to regenerate the regions of the island. + * + * @param gm the game mode + * @param di the island deletion + * @param world the world + * @return the completable future + */ + CompletableFuture regenerate(GameModeAddon gm, IslandDeletion di, World world); +} diff --git a/src/main/java/world/bentobox/bentobox/nms/fallback/NMSHandler.java b/src/main/java/world/bentobox/bentobox/nms/fallback/NMSHandler.java deleted file mode 100644 index 23285dc4b..000000000 --- a/src/main/java/world/bentobox/bentobox/nms/fallback/NMSHandler.java +++ /dev/null @@ -1,20 +0,0 @@ -package world.bentobox.bentobox.nms.fallback; - -import org.bukkit.Chunk; -import org.bukkit.block.data.BlockData; - -import world.bentobox.bentobox.nms.NMSAbstraction; - -/** - * @author tastybento - * - */ -public class NMSHandler implements NMSAbstraction { - - @Override - public void setBlockInNativeChunk(Chunk chunk, int x, int y, int z, BlockData blockData, boolean applyPhysics) { - chunk.getBlock(x, y, z).setBlockData(blockData, applyPhysics); - } - - -} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/nms/fallback/WorldRegeneratorImpl.java b/src/main/java/world/bentobox/bentobox/nms/fallback/WorldRegeneratorImpl.java new file mode 100644 index 000000000..60d356359 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/nms/fallback/WorldRegeneratorImpl.java @@ -0,0 +1,20 @@ +package world.bentobox.bentobox.nms.fallback; + +import org.bukkit.Chunk; +import org.bukkit.block.data.BlockData; + +import world.bentobox.bentobox.nms.SimpleWorldRegenerator; + +/** + * @author tastybento + * + */ +public class WorldRegeneratorImpl extends SimpleWorldRegenerator { + + @Override + protected void setBlockInNativeChunk(Chunk chunk, int x, int y, int z, BlockData blockData, boolean applyPhysics) { + chunk.getBlock(x, y, z).setBlockData(blockData, applyPhysics); + } + + +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/bentobox/nms/v1_18_R1/NMSHandler.java b/src/main/java/world/bentobox/bentobox/nms/v1_18_R2/WorldRegeneratorImpl.java similarity index 78% rename from src/main/java/world/bentobox/bentobox/nms/v1_18_R1/NMSHandler.java rename to src/main/java/world/bentobox/bentobox/nms/v1_18_R2/WorldRegeneratorImpl.java index f0585bde3..6754ecd02 100644 --- a/src/main/java/world/bentobox/bentobox/nms/v1_18_R1/NMSHandler.java +++ b/src/main/java/world/bentobox/bentobox/nms/v1_18_R2/WorldRegeneratorImpl.java @@ -1,19 +1,19 @@ -package world.bentobox.bentobox.nms.v1_18_R1; +package world.bentobox.bentobox.nms.v1_18_R2; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.data.BlockData; -import org.bukkit.craftbukkit.v1_18_R1.CraftWorld; -import org.bukkit.craftbukkit.v1_18_R1.block.data.CraftBlockData; +import org.bukkit.craftbukkit.v1_18_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_18_R2.block.data.CraftBlockData; import net.minecraft.core.BlockPosition; import net.minecraft.world.level.World; import net.minecraft.world.level.block.state.IBlockData; import net.minecraft.world.level.chunk.Chunk; -import world.bentobox.bentobox.nms.NMSAbstraction; +import world.bentobox.bentobox.nms.SimpleWorldRegenerator; -public class NMSHandler implements NMSAbstraction { +public class WorldRegeneratorImpl extends SimpleWorldRegenerator { private static final IBlockData AIR = ((CraftBlockData) Bukkit.createBlockData(Material.AIR)).getState(); diff --git a/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java b/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java index 0f1c88602..a9be0f62b 100644 --- a/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java +++ b/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java @@ -1,29 +1,16 @@ package world.bentobox.bentobox.util; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.bukkit.Bukkit; -import org.bukkit.Chunk; import org.bukkit.World; -import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; -import org.bukkit.generator.ChunkGenerator; -import org.bukkit.generator.ChunkGenerator.ChunkData; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.scheduler.BukkitTask; - -import io.papermc.lib.PaperLib; +import org.bukkit.scheduler.BukkitRunnable; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.events.island.IslandEvent; import world.bentobox.bentobox.api.events.island.IslandEvent.Reason; import world.bentobox.bentobox.database.objects.IslandDeletion; -import world.bentobox.bentobox.nms.NMSAbstraction; +import world.bentobox.bentobox.nms.WorldRegenerator; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; /** * Deletes islands chunk by chunk @@ -37,16 +24,10 @@ public class DeleteIslandChunks { private final World netherWorld; private final World endWorld; private final AtomicBoolean completed; - private final NMSAbstraction nms; - private int chunkX; - private int chunkZ; - private BukkitTask task; - private CompletableFuture currentTask = CompletableFuture.completedFuture(null); + private final WorldRegenerator regenerator; public DeleteIslandChunks(BentoBox plugin, IslandDeletion di) { this.plugin = plugin; - this.chunkX = di.getMinXChunk(); - this.chunkZ = di.getMinZChunk(); this.di = di; completed = new AtomicBoolean(false); // Nether @@ -61,9 +42,9 @@ public class DeleteIslandChunks { } else { endWorld = null; } - // NMS - this.nms = Util.getNMS(); - if (nms == null) { + // Regenerator + this.regenerator = Util.getRegenerator(); + if (regenerator == null) { plugin.logError("Could not delete chunks because of NMS error"); return; } @@ -75,37 +56,23 @@ public class DeleteIslandChunks { } private void regenerateChunks() { - // Run through all chunks of the islands and regenerate them. - task = Bukkit.getScheduler().runTaskTimer(plugin, () -> { - if (!currentTask.isDone()) return; - if (isEnded(chunkX)) { - finish(); - return; - } - List> newTasks = new ArrayList<>(); - for (int i = 0; i < plugin.getSettings().getDeleteSpeed(); i++) { - if (isEnded(chunkX)) { - break; - } - final int x = chunkX; - final int z = chunkZ; - plugin.getIWM().getAddon(di.getWorld()).ifPresent(gm -> { - newTasks.add(processChunk(gm, di.getWorld(), x, z)); // Overworld - newTasks.add(processChunk(gm, netherWorld, x, z)); // Nether - newTasks.add(processChunk(gm, endWorld, x, z)); // End - }); - chunkZ++; - if (chunkZ > di.getMaxZChunk()) { - chunkZ = di.getMinZChunk(); - chunkX++; + CompletableFuture all = plugin.getIWM().getAddon(di.getWorld()) + .map(gm -> new CompletableFuture[]{ + processWorld(gm, di.getWorld()), // Overworld + processWorld(gm, netherWorld), // Nether + processWorld(gm, endWorld) // End + }) + .map(CompletableFuture::allOf) + .orElseGet(() -> CompletableFuture.completedFuture(null)); + new BukkitRunnable() { + @Override + public void run() { + if (all.isDone()) { + finish(); + cancel(); } } - currentTask = CompletableFuture.allOf(newTasks.toArray(new CompletableFuture[0])); - }, 0L, 20L); - } - - private boolean isEnded(int chunkX) { - return chunkX > di.getMaxXChunk(); + }.runTaskTimer(plugin, 0, 20); } private void finish() { @@ -113,44 +80,16 @@ public class DeleteIslandChunks { IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETED).build(); // We're done completed.set(true); - task.cancel(); } - private CompletableFuture processChunk(GameModeAddon gm, World world, int x, int z) { + private CompletableFuture processWorld(GameModeAddon gm, World world) { if (world != null) { - return PaperLib.getChunkAtAsync(world, x, z).thenAccept(chunk -> regenerateChunk(gm, chunk, x, z)); + return regenerator.regenerate(gm, di, world); } else { return CompletableFuture.completedFuture(null); } } - private void regenerateChunk(GameModeAddon gm, Chunk chunk, int x, int z) { - // Clear all inventories - Arrays.stream(chunk.getTileEntities()).filter(InventoryHolder.class::isInstance) - .filter(te -> di.inBounds(te.getLocation().getBlockX(), te.getLocation().getBlockZ())) - .forEach(te -> ((InventoryHolder) te).getInventory().clear()); - // Remove all entities - for (Entity e : chunk.getEntities()) { - if (!(e instanceof Player)) { - e.remove(); - } - } - // Reset blocks - MyBiomeGrid grid = new MyBiomeGrid(chunk.getWorld().getEnvironment()); - ChunkGenerator cg = gm.getDefaultWorldGenerator(chunk.getWorld().getName(), "delete"); - // Will be null if use-own-generator is set to true - if (cg != null) { - ChunkData cd = cg.generateChunkData(chunk.getWorld(), new Random(), chunk.getX(), chunk.getZ(), grid); - createChunk(cd, chunk, grid); - } - } - - private void createChunk(ChunkData cd, Chunk chunk, MyBiomeGrid grid) { - nms.copyChunkDataToChunk(chunk, cd, grid, di.getBox()); - // Remove all entities in chunk, including any dropped items as a result of clearing the blocks above - Arrays.stream(chunk.getEntities()).filter(e -> !(e instanceof Player) && di.inBounds(e.getLocation().getBlockX(), e.getLocation().getBlockZ())).forEach(Entity::remove); - } - public boolean isCompleted() { return completed.get(); } diff --git a/src/main/java/world/bentobox/bentobox/util/Util.java b/src/main/java/world/bentobox/bentobox/util/Util.java index f2ef681f8..c0a049119 100644 --- a/src/main/java/world/bentobox/bentobox/util/Util.java +++ b/src/main/java/world/bentobox/bentobox/util/Util.java @@ -47,7 +47,7 @@ import io.papermc.lib.PaperLib; import io.papermc.lib.features.blockstatesnapshot.BlockStateSnapshotResult; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.nms.NMSAbstraction; +import world.bentobox.bentobox.nms.WorldRegenerator; /** * A set of utility methods @@ -64,7 +64,7 @@ public class Util { private static final String THE_END = "_the_end"; private static String serverVersion = null; private static BentoBox plugin = BentoBox.getInstance(); - private static NMSAbstraction nms = null; + private static WorldRegenerator regenerator = null; private Util() {} @@ -689,23 +689,37 @@ public class Util { } /** - * Set the NMS handler the plugin will use - * @param nms the NMS handler + * Set the regenerator the plugin will use + * @param regenerator the regenerator */ - public static void setNms(NMSAbstraction nms) { - Util.nms = nms; + public static void setRegenerator(WorldRegenerator regenerator) { + Util.regenerator = regenerator; } /** - * Get the NMS handler the plugin will use - * @return an NMS accelerated class for this server + * Get the regenerator the plugin will use + * @return an accelerated regenerator class for this server */ - public static NMSAbstraction getNMS() { - if (nms == null) { - plugin.log("No NMS Handler was set, falling back to Bukkit API."); - setNms(new world.bentobox.bentobox.nms.fallback.NMSHandler()); + public static WorldRegenerator getRegenerator() { + if (regenerator == null) { + String serverPackageName = Bukkit.getServer().getClass().getPackage().getName(); + String pluginPackageName = plugin.getClass().getPackage().getName(); + String version = serverPackageName.substring(serverPackageName.lastIndexOf('.') + 1); + WorldRegenerator handler; + try { + Class clazz = Class.forName(pluginPackageName + ".nms." + version + ".WorldRegeneratorImpl"); + if (WorldRegenerator.class.isAssignableFrom(clazz)) { + handler = (WorldRegenerator) clazz.getConstructor().newInstance(); + } else { + throw new IllegalStateException("Class " + clazz.getName() + " does not implement WorldRegenerator"); + } + } catch (Exception e) { + plugin.logWarning("No Regenerator found for " + version + ", falling back to Bukkit API."); + handler = new world.bentobox.bentobox.nms.fallback.WorldRegeneratorImpl(); + } + setRegenerator(handler); } - return nms; + return regenerator; } /**