From 650e370ffe5ee11e1fd80c4af18ae679db144783 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 1 May 2019 07:25:35 -0700 Subject: [PATCH] Rewrote chunk deletion (#648) As of 1.14, chunk regeneration is no longer supported. This PR implements a not-chunk-based deletion that supports both 1.13 and 1.14 and which also allows us to get rid of the "multiple of 16" rule for island distances. This PR however does not remove the "multiple of 16" rule and a commit should be made thereafter. This PR is also a pre-requisite to #640. * Makes GameModes responsible for regenerating chunks. * Deletes chunks manually to solve 1.14 chunk regen removal * Fixes round up to 16 for island distance bug. * Clean up - removing imports and stack traces * Revert "Fixes round up to 16 for island distance bug." This reverts commit 54f1ce0940a4882fb2499a77d0fb954d7cbcb128. * Adds island edge protection for deletion. Needs full testing. * Completed testing. Works correctly. --- .../bentobox/api/addons/GameModeAddon.java | 13 +-- .../database/objects/IslandDeletion.java | 90 +++++++++++++--- .../bentobox/util/DeleteIslandChunks.java | 102 +++++++++++++----- .../util/teleport/SafeSpotTeleport.java | 7 +- 4 files changed, 165 insertions(+), 47 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/api/addons/GameModeAddon.java b/src/main/java/world/bentobox/bentobox/api/addons/GameModeAddon.java index 63ff58583..e4867eddd 100644 --- a/src/main/java/world/bentobox/bentobox/api/addons/GameModeAddon.java +++ b/src/main/java/world/bentobox/bentobox/api/addons/GameModeAddon.java @@ -123,10 +123,11 @@ public abstract class GameModeAddon extends Addon { @NonNull public abstract ChunkGenerator getDefaultWorldGenerator(String worldName, String id); - /** - * Tells the Game Mode Addon to save its settings. Used when world settings are changed - * in-game and need to be saved. - * @since 1.4.0 - */ - public abstract void saveWorldSettings(); + /** + * Tells the Game Mode Addon to save its settings. Used when world settings are changed + * in-game and need to be saved. + * @since 1.4.0 + */ + public abstract void saveWorldSettings(); + } diff --git a/src/main/java/world/bentobox/bentobox/database/objects/IslandDeletion.java b/src/main/java/world/bentobox/bentobox/database/objects/IslandDeletion.java index 2f3a59aba..d1a0b837b 100644 --- a/src/main/java/world/bentobox/bentobox/database/objects/IslandDeletion.java +++ b/src/main/java/world/bentobox/bentobox/database/objects/IslandDeletion.java @@ -4,6 +4,8 @@ import java.util.UUID; import org.bukkit.Location; import org.bukkit.World; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.Vector; import com.google.gson.annotations.Expose; @@ -32,24 +34,35 @@ public class IslandDeletion implements DataObject { @Expose private int maxZChunk; + @Expose + private int minX; + + @Expose + private int minZ; + + @Expose + private int maxX; + + @Expose + private int maxZ; + + @Expose + BoundingBox box; + public IslandDeletion() {} public IslandDeletion(Island island) { uniqueId = UUID.randomUUID().toString(); location = island.getCenter(); - minXChunk = (location.getBlockX() - island.getMaxEverProtectionRange()) >> 4; - maxXChunk = (island.getMaxEverProtectionRange() + location.getBlockX() - 1) >> 4; - minZChunk = (location.getBlockZ() - island.getMaxEverProtectionRange()) >> 4; - maxZChunk = (island.getMaxEverProtectionRange() + location.getBlockZ() - 1) >> 4; - } - - public IslandDeletion(Location location, int minXChunk, int maxXChunk, int minZChunk, int maxZChunk) { - this.uniqueId = UUID.randomUUID().toString(); - this.location = location; - this.minXChunk = minXChunk; - this.maxXChunk = maxXChunk; - this.minZChunk = minZChunk; - this.maxZChunk = maxZChunk; + minX = location.getBlockX() - island.getMaxEverProtectionRange(); + minXChunk = minX >> 4; + maxX = island.getMaxEverProtectionRange() + location.getBlockX(); + maxXChunk = maxX >> 4; + minZ = location.getBlockZ() - island.getMaxEverProtectionRange(); + minZChunk = minZ >> 4; + maxZ = island.getMaxEverProtectionRange() + location.getBlockZ(); + maxZChunk = maxZ >> 4; + box = BoundingBox.of(new Vector(minX, 0, minZ), new Vector(maxX, 255, maxZ)); } /* (non-Javadoc) @@ -170,9 +183,60 @@ public class IslandDeletion implements DataObject { this.minZChunk = minZChunk; } + public int getMinX() { + return minX; + } + + public void setMinX(int minX) { + this.minX = minX; + } + + public int getMinZ() { + return minZ; + } + + public void setMinZ(int minZ) { + this.minZ = minZ; + } + + public int getMaxX() { + return maxX; + } + + public void setMaxX(int maxX) { + this.maxX = maxX; + } + + public int getMaxZ() { + return maxZ; + } + + public void setMaxZ(int maxZ) { + this.maxZ = maxZ; + } + @Override public void setUniqueId(String uniqueId) { this.uniqueId = uniqueId; } + + public boolean inBounds(int x, int z) { + return box.contains(new Vector(x, 0, z)); + } + + /** + * @return the box + */ + public BoundingBox getBox() { + return box; + } + + /** + * @param box the box to set + */ + public void setBox(BoundingBox box) { + this.box = box; + } + } diff --git a/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java b/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java index ea24320df..e819501ac 100644 --- a/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java +++ b/src/main/java/world/bentobox/bentobox/util/DeleteIslandChunks.java @@ -1,15 +1,29 @@ package world.bentobox.bentobox.util; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.block.Biome; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.generator.ChunkGenerator.BiomeGrid; +import org.bukkit.generator.ChunkGenerator.ChunkData; +import org.bukkit.inventory.InventoryHolder; import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.Vector; 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; /** - * Deletes islands fast using chunk regeneration + * Deletes islands chunk by chunk * * @author tastybento */ @@ -19,39 +33,79 @@ public class DeleteIslandChunks { * This is how many chunks per world will be done in one tick. */ private static final int SPEED = 5; - private int x; - private int z; + private int chunkX; + private int chunkZ; private BukkitTask task; + private IslandDeletion di; - @SuppressWarnings({"deprecation", "squid:CallToDeprecatedMethod"}) public DeleteIslandChunks(BentoBox plugin, IslandDeletion di) { // Fire event IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETE_CHUNKS).build(); - x = di.getMinXChunk(); - z = di.getMinZChunk(); + this.chunkX = di.getMinXChunk(); + this.chunkZ = di.getMinZChunk(); + this.di = di; // Run through all chunks of the islands and regenerate them. task = Bukkit.getScheduler().runTaskTimer(plugin, () -> { for (int i = 0; i < SPEED; i++) { - // World#regenerateChunk(int, int) from Bukkit is deprecated because it may not regenerate decoration correctly - di.getWorld().regenerateChunk(x, z); - if (plugin.getIWM().isNetherGenerate(di.getWorld()) && plugin.getIWM().isNetherIslands(di.getWorld())) { - plugin.getIWM().getNetherWorld(di.getWorld()).regenerateChunk(x, z); - } - if (plugin.getIWM().isEndGenerate(di.getWorld()) && plugin.getIWM().isEndIslands(di.getWorld())) { - plugin.getIWM().getEndWorld(di.getWorld()).regenerateChunk(x, z); - } - z++; - if (z > di.getMaxZChunk()) { - z = di.getMinZChunk(); - x++; - if (x > di.getMaxXChunk()) { - // We're done - task.cancel(); - // Fire event - IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETED).build(); + plugin.getIWM().getAddon(di.getWorld()).ifPresent(gm -> { + + regerateChunk(gm, di.getWorld().getChunkAt(chunkX, chunkZ)); + + if (plugin.getIWM().isNetherGenerate(di.getWorld()) && plugin.getIWM().isNetherIslands(di.getWorld())) { + regerateChunk(gm, plugin.getIWM().getNetherWorld(di.getWorld()).getChunkAt(chunkX, chunkZ)); } - } + if (plugin.getIWM().isEndGenerate(di.getWorld()) && plugin.getIWM().isEndIslands(di.getWorld())) { + regerateChunk(gm, plugin.getIWM().getEndWorld(di.getWorld()).getChunkAt(chunkX, chunkZ)); + } + chunkZ++; + if (chunkZ > di.getMaxZChunk()) { + chunkZ = di.getMinZChunk(); + chunkX++; + if (chunkX > di.getMaxXChunk()) { + // We're done + task.cancel(); + // Fire event + IslandEvent.builder().deletedIslandInfo(di).reason(Reason.DELETED).build(); + } + } + }); } }, 0L, 1L); } + + private void regerateChunk(GameModeAddon gm, Chunk chunk) { + // Clear all inventories + Arrays.stream(chunk.getTileEntities()).filter(te -> (te instanceof InventoryHolder)) + .filter(te -> di.inBounds(te.getLocation().getBlockX(), te.getLocation().getBlockZ())) + .forEach(te -> ((InventoryHolder)te).getInventory().clear()); + // Reset blocks + MyBiomeGrid grid = new MyBiomeGrid(); + ChunkData cd = gm.getDefaultWorldGenerator(chunk.getWorld().getName(), "").generateChunkData(chunk.getWorld(), new Random(), chunk.getX(), chunk.getZ(), grid); + int baseX = chunk.getX() << 4; + int baseZ = chunk.getZ() << 4; + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + 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++) { + chunk.getBlock(x, y, z).setBlockData(cd.getBlockData(x, y, z)); + } + } + } + } + // 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); + } + + class MyBiomeGrid implements BiomeGrid { + Map map = new HashMap<>(); + @Override + public Biome getBiome(int x, int z) { + return map.getOrDefault(new Vector(x,0,z), Biome.PLAINS); + } + @Override + public void setBiome(int x, int z, Biome bio) { + map.put(new Vector(x,0,z), bio); + } + } } 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 943836cb2..c49640933 100644 --- a/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java +++ b/src/main/java/world/bentobox/bentobox/util/teleport/SafeSpotTeleport.java @@ -31,6 +31,7 @@ public class SafeSpotTeleport { private static final int MAX_CHUNKS = 200; private static final long SPEED = 1; private static final int MAX_RADIUS = 200; + private static final int MAX_HEIGHT = 235; private boolean checking; private BukkitTask task; @@ -219,14 +220,12 @@ public class SafeSpotTeleport { * @return true if a safe spot was found */ private boolean scanChunk(ChunkSnapshot chunk) { - // Max height - int maxHeight = location.getWorld().getMaxHeight() - 20; // Run through the chunk for (int x = 0; x< 16; x++) { for (int z = 0; z < 16; z++) { // Work down from the entry point up - for (int y = Math.min(chunk.getHighestBlockYAt(x, z), maxHeight); y >= 0; y--) { - if (checkBlock(chunk, x,y,z, maxHeight)) { + for (int y = Math.min(chunk.getHighestBlockYAt(x, z), MAX_HEIGHT); y >= 0; y--) { + if (checkBlock(chunk, x,y,z, MAX_HEIGHT)) { return true; } } // end y