diff --git a/src/main/java/com/iridium/iridiumskyblock/configs/Configuration.java b/src/main/java/com/iridium/iridiumskyblock/configs/Configuration.java index d4d61da47..b53d9be80 100644 --- a/src/main/java/com/iridium/iridiumskyblock/configs/Configuration.java +++ b/src/main/java/com/iridium/iridiumskyblock/configs/Configuration.java @@ -87,4 +87,5 @@ public Configuration() { .put(Color.OFF, true) .build(); + public boolean tpBypassersWithRegen = true; } diff --git a/src/main/java/com/iridium/iridiumskyblock/managers/IslandManager.java b/src/main/java/com/iridium/iridiumskyblock/managers/IslandManager.java index 35178c595..e2ae62146 100644 --- a/src/main/java/com/iridium/iridiumskyblock/managers/IslandManager.java +++ b/src/main/java/com/iridium/iridiumskyblock/managers/IslandManager.java @@ -25,9 +25,26 @@ import com.iridium.iridiumteams.missions.Mission; import com.iridium.iridiumteams.missions.MissionData; import com.iridium.iridiumteams.missions.MissionType; +import com.iridium.iridiumteams.support.SpawnSupport; import com.iridium.iridiumteams.support.StackerSupport; import com.j256.ormlite.stmt.QueryBuilder; import com.j256.ormlite.stmt.Where; +import com.sk89q.worldedit.*; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.extent.MaskingExtent; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; +import com.sk89q.worldedit.function.mask.ExistingBlockMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.operation.ForwardExtentCopy; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.world.block.BlockTypes; import de.tr7zw.changeme.nbtapi.NBT; import de.tr7zw.changeme.nbtapi.NBTCompound; import de.tr7zw.changeme.nbtapi.NBTFile; @@ -44,6 +61,7 @@ import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitRunnable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -141,8 +159,8 @@ public void setIslandBiome(@NotNull Island island, @NotNull XBiome biome) { if (world == null) return; getIslandChunks(island).thenAccept(chunks -> { - Location pos1 = island.getPosition1(world); - Location pos2 = island.getPosition2(world); + Location pos1 = island.getMaximumPosition1(world); + Location pos2 = island.getMaximumPosition2(world); biome.setBiome(pos1, pos2).thenRun(() -> { for (Chunk chunk : chunks) { chunk.getWorld().refreshChunk(chunk.getX(), chunk.getZ()); @@ -301,9 +319,11 @@ public CompletableFuture generateIsland(Island island, Schematics.Schemati return CompletableFuture.runAsync(() -> { setHome(island, schematicConfig); clearEntities(island); + removePlayers(island).join(); deleteIslandBlocks(island).join(); - if (IridiumSkyblock.getInstance().getConfiguration().generatorType.isTerrainGenerator()) + if (IridiumSkyblock.getInstance().getConfiguration().generatorType.isTerrainGenerator()) { regenerateTerrain(island).join(); + } IridiumSkyblock.getInstance().getSchematicManager().pasteSchematic(island, schematicConfig).join(); setIslandBiome(island, schematicConfig); }); @@ -353,6 +373,55 @@ private void clearEntities(Island island, World world, CompletableFuture c completableFuture.complete(null); } + public CompletableFuture removePlayers(Island island) { + + return CompletableFuture.runAsync(() -> { + List> completableFutures = Arrays.asList( + removePlayers(island, getWorld(World.Environment.NORMAL)), + removePlayers(island, getWorld(World.Environment.NETHER)), + removePlayers(island, getWorld(World.Environment.THE_END)) + ); + completableFutures.forEach(CompletableFuture::join); + }); + } + + private CompletableFuture removePlayers(Island island, World world) { + CompletableFuture completableFuture = new CompletableFuture<>(); + if (world == null) { + completableFuture.complete(null); + } else { + Bukkit.getScheduler().runTask(IridiumSkyblock.getInstance(), () -> removePlayers(island, world, completableFuture)); + } + return completableFuture; + } + + private void removePlayers(Island island, World world, CompletableFuture completableFuture) { + + if (world == null) return; + + Optional> spawnSupport = IridiumSkyblock.getInstance().getSupportManager().getSpawnSupport().stream().findFirst(); + + world.getPlayers().forEach(player -> { + + if (island.isInIsland(player.getLocation())) { + + boolean bypasser = IridiumSkyblock.getInstance().getUserManager().getUser(player).isBypassing(); + + // If a player is bypassing island restrictions, they're likely an admin. + // We don't want to kick admins from an island if they're watching it. + if (!bypasser || (bypasser && IridiumSkyblock.getInstance().getConfiguration().tpBypassersWithRegen)) { // if not bypassing OR bypassing and should be teleported + if (spawnSupport.isPresent()) { + player.teleport(spawnSupport.get().getSpawn(player)); + } else { + player.teleport(Bukkit.getServer().getWorlds().get(0).getSpawnLocation()); + } + } + } + }); + + completableFuture.complete(null); + } + public CompletableFuture deleteIslandBlocks(Island island) { return CompletableFuture.runAsync(() -> { List> completableFutures = Arrays.asList( @@ -369,37 +438,25 @@ private CompletableFuture deleteIslandBlocks(Island island, World world) { if (world == null) { completableFuture.complete(null); } else { - Bukkit.getScheduler().runTask(IridiumSkyblock.getInstance(), () -> deleteIslandBlocks(island, world, world.getMaxHeight(), completableFuture, 0)); + Bukkit.getScheduler().runTask(IridiumSkyblock.getInstance(), () -> + deleteIslandBlocks(island, world, completableFuture, IridiumSkyblock.getInstance().getConfiguration().pasterDelayInTick)); } return completableFuture; } - private void deleteIslandBlocks(Island island, World world, int y, CompletableFuture completableFuture, int delay) { + private void deleteIslandBlocks(Island island, World world, CompletableFuture completableFuture, int delay) { + if (world == null) return; + Location pos1 = island.getMaximumPosition1(world); Location pos2 = island.getMaximumPosition2(world); - for (int x = pos1.getBlockX(); x <= pos2.getBlockX(); x++) { - for (int z = pos1.getBlockZ(); z <= pos2.getBlockZ(); z++) { - Block block = world.getBlockAt(x, y, z); - if (block.getType() != Material.AIR) { - if (block.getState() instanceof InventoryHolder) { - ((InventoryHolder) block.getState()).getInventory().clear(); - } - block.setType(Material.AIR, false); - } - } - } + int minHeight = 0; + if (XReflection.supports(18)) minHeight = world.getMinHeight(); + final int finalMinHeight = minHeight; + int maxHeight = world.getMaxHeight(); - if (y <= LocationUtils.getMinHeight(world)) { - completableFuture.complete(null); - } else { - if (delay < 1) { - deleteIslandBlocks(island, world, y - 1, completableFuture, delay); - } else { - Bukkit.getScheduler().runTaskLater(IridiumSkyblock.getInstance(), () -> deleteIslandBlocks(island, world, y - 1, completableFuture, delay), delay); - } - } + IridiumSkyblock.getInstance().getSchematicManager().deleteIsland(world, pos1, pos2, finalMinHeight, maxHeight, completableFuture, delay); } public CompletableFuture regenerateTerrain(Island island) { @@ -418,37 +475,29 @@ private CompletableFuture regenerateTerrain(Island island, World world) { if (world == null) { completableFuture.complete(null); } else { - Bukkit.getScheduler().runTask(IridiumSkyblock.getInstance(), () -> regenerateTerrain(island, world, world.getMaxHeight(), completableFuture, 0)); + Bukkit.getScheduler().runTask(IridiumSkyblock.getInstance(), () -> + regenerateTerrain(island, world, completableFuture, IridiumSkyblock.getInstance().getConfiguration().pasterDelayInTick)); } return completableFuture; } - public void regenerateTerrain(Island island, World world, int y, CompletableFuture completableFuture, int delay) { + public void regenerateTerrain(Island island, World world, CompletableFuture completableFuture, int delay) { if (world == null) return; - Location pos1 = island.getMaximumPosition1(world); - Location pos2 = island.getMaximumPosition2(world); - World regenWorld = Bukkit.getWorld(getCacheWorldName(world)); - for (int x = pos1.getBlockX(); x <= pos2.getBlockX(); x++) { - for (int z = pos1.getBlockZ(); z <= pos2.getBlockZ(); z++) { - Block blockA = regenWorld.getBlockAt(x, y, z); - Block blockB = world.getBlockAt(x, y, z); - blockB.setBlockData(blockA.getBlockData(), false); - } - } + Location pos1 = island.getMaximumPosition1(regenWorld); + Location pos2 = island.getMaximumPosition2(regenWorld); + Location pos3 = island.getMaximumPosition1(world); + Location pos4 = island.getMaximumPosition2(world); - if (y <= LocationUtils.getMinHeight(world)) { - completableFuture.complete(null); - } else { - if (delay < 1) { - regenerateTerrain(island, world, y - 1, completableFuture, delay); - } else { - Bukkit.getScheduler().runTaskLater(IridiumSkyblock.getInstance(), () -> regenerateTerrain(island, world, y - 1, completableFuture, delay), delay); - } - } + int minHeight = 0; + if (XReflection.supports(18)) minHeight = world.getMinHeight(); + final int finalMinHeight = minHeight; + int maxHeight = world.getMaxHeight(); + + IridiumSkyblock.getInstance().getSchematicManager().regenIsland(world, regenWorld, pos1, pos2, pos3, pos4, finalMinHeight, maxHeight, completableFuture, delay); } @Override @@ -1065,5 +1114,4 @@ public void clearTeamInventory(Island island) { } } } - } diff --git a/src/main/java/com/iridium/iridiumskyblock/managers/SchematicManager.java b/src/main/java/com/iridium/iridiumskyblock/managers/SchematicManager.java index 470f9bb55..e471d8827 100644 --- a/src/main/java/com/iridium/iridiumskyblock/managers/SchematicManager.java +++ b/src/main/java/com/iridium/iridiumskyblock/managers/SchematicManager.java @@ -1,5 +1,6 @@ package com.iridium.iridiumskyblock.managers; +import com.iridium.iridiumcore.multiversion.IridiumInventory; import com.iridium.iridiumcore.utils.StringUtils; import com.iridium.iridiumskyblock.IridiumSkyblock; import com.iridium.iridiumskyblock.configs.Schematics; @@ -10,12 +11,33 @@ import com.iridium.iridiumskyblock.schematics.SchematicPaster; import com.iridium.iridiumskyblock.schematics.WorldEdit; import com.iridium.iridiumteams.database.TeamBank; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.extent.MaskingExtent; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; +import com.sk89q.worldedit.function.mask.ExistingBlockMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.operation.ForwardExtentCopy; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.math.BlockVector2; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.world.block.BlockTypes; import net.milkbowl.vault.economy.Economy; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.block.Block; import org.bukkit.entity.Player; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.scheduler.BukkitRunnable; import java.io.File; import java.util.*; @@ -175,4 +197,22 @@ public String formatPrice(double value) { return String.valueOf(value); } + // IslandManager calls deleteIsland() and regenIsland() for each world. + // This is intentional, as we'll be reworking the regen command to only regenerate a specific dimension. + + public CompletableFuture deleteIsland(World world, Location pos1, Location pos2, int minHeight, int maxHeight, CompletableFuture completableFuture, int delay) { + + Bukkit.getScheduler().runTask(IridiumSkyblock.getInstance(), () -> { + schematicPaster.deleteIsland(world, pos1, pos2, minHeight, maxHeight, completableFuture, delay); + }); + return completableFuture; + } + + public CompletableFuture regenIsland(World world, World regenWorld, Location pos1, Location pos2, Location pos3, Location pos4, int minHeight, int maxHeight, CompletableFuture completableFuture, int delay) { + + Bukkit.getScheduler().runTask(IridiumSkyblock.getInstance(), () -> { + schematicPaster.regenIsland(world, regenWorld, pos1, pos2, pos3, pos4, minHeight, maxHeight, completableFuture, delay); + }); + return completableFuture; + } } diff --git a/src/main/java/com/iridium/iridiumskyblock/schematics/FastAsyncWorldEdit.java b/src/main/java/com/iridium/iridiumskyblock/schematics/FastAsyncWorldEdit.java index e1f7c4925..851dc6fc5 100644 --- a/src/main/java/com/iridium/iridiumskyblock/schematics/FastAsyncWorldEdit.java +++ b/src/main/java/com/iridium/iridiumskyblock/schematics/FastAsyncWorldEdit.java @@ -3,19 +3,29 @@ import com.iridium.iridiumskyblock.IridiumSkyblock; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; +import com.sk89q.worldedit.function.mask.ExistingBlockMask; +import com.sk89q.worldedit.function.operation.ForwardExtentCopy; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.util.SideEffectSet; +import com.sk89q.worldedit.world.block.BlockTypes; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.scheduler.BukkitRunnable; import java.io.File; import java.io.FileInputStream; @@ -44,6 +54,9 @@ public static boolean isWorking() { @Override public void paste(File file, Location location, Boolean ignoreAirBlock, CompletableFuture completableFuture) { + + + try { ClipboardFormat format = cachedClipboardFormat.getOrDefault(file, ClipboardFormats.findByFile(file)); ClipboardReader reader = format.getReader(new FileInputStream(file)); @@ -99,4 +112,92 @@ public void run() { public void clearCache() { cachedClipboardFormat.clear(); } + + public void deleteIsland(World world, Location pos1, Location pos2, int minHeight, int maxHeight, CompletableFuture completableFuture, int delay) { + + com.sk89q.worldedit.world.World weWorld = BukkitAdapter.adapt(world); + + try (EditSession editSession = com.sk89q.worldedit.WorldEdit.getInstance().newEditSession(weWorld)) { + BlockVector3 min = BlockVector3.at(pos1.getBlockX(), minHeight, pos1.getBlockZ()); + BlockVector3 max = BlockVector3.at(pos2.getBlockX(), maxHeight, pos2.getBlockZ()); + CuboidRegion selection = new CuboidRegion(min, max); + + com.sk89q.worldedit.world.block.BlockState air = BlockTypes.AIR.getDefaultState(); + ExistingBlockMask nonAirMask = new ExistingBlockMask(editSession); + + editSession.getChangeSet().setRecordChanges(false); + editSession.setReorderMode(EditSession.ReorderMode.FAST); + editSession.setSideEffectApplier(SideEffectSet.none()); + + editSession.replaceBlocks(selection, nonAirMask, (Pattern) air); // no style points :( + + new BukkitRunnable() { + @Override + public void run() { + if (!IridiumSkyblock.getInstance().getConfiguration().generatorType.isTerrainGenerator()) { + weWorld.fixLighting(selection.getChunks()); + } + } + }.runTask(IridiumSkyblock.getInstance()); + + } catch (WorldEditException e) { + throw new RuntimeException(e); + } + + completableFuture.complete(null); + } + + public void regenIsland(World world, World regenWorld, Location pos1, Location pos2, Location pos3, Location pos4, int minHeight, int maxHeight, CompletableFuture completableFuture, int delay) { + + com.sk89q.worldedit.world.World weWorld = BukkitAdapter.adapt(world); + com.sk89q.worldedit.world.World weRegenWorld = BukkitAdapter.adapt(regenWorld); + + BlockVector3 min = BlockVector3.at(pos1.getBlockX(), minHeight, pos1.getBlockZ()); + BlockVector3 max = BlockVector3.at(pos2.getBlockX(), maxHeight, pos2.getBlockZ()); + CuboidRegion selection = new CuboidRegion(min, max); + + BlockVector3 min2 = BlockVector3.at(pos3.getBlockX(), minHeight, pos3.getBlockZ()); + BlockVector3 max2 = BlockVector3.at(pos4.getBlockX(), maxHeight, pos4.getBlockZ()); + CuboidRegion selection2 = new CuboidRegion(min2, max2); + + BlockArrayClipboard clipboard = new BlockArrayClipboard(selection); + + // copy + try (EditSession editSession = com.sk89q.worldedit.WorldEdit.getInstance().newEditSession(weRegenWorld)) { + editSession.getChangeSet().setRecordChanges(false); + ExistingBlockMask nonAirMask = new ExistingBlockMask(editSession); + editSession.setMask(nonAirMask); + + ForwardExtentCopy forwardExtentCopy = new ForwardExtentCopy( + weRegenWorld, + selection, + clipboard, + selection.getMinimumPoint() + ); + + Operations.complete(forwardExtentCopy); + + } catch (WorldEditException e) { + throw new RuntimeException(e); + } + + // paste + try (EditSession editSession = com.sk89q.worldedit.WorldEdit.getInstance().newEditSession(weWorld)) { + editSession.getChangeSet().setRecordChanges(false); + + Operation operation = new ClipboardHolder(clipboard) + .createPaste(editSession) + .to(selection2.getMinimumPoint()) + .copyBiomes(true) + .copyEntities(false) + .build(); + + Operations.complete(operation); + } catch (WorldEditException e) { + throw new RuntimeException(e); + } + + completableFuture.complete(null); + weWorld.fixLighting(selection.getChunks()); + } } diff --git a/src/main/java/com/iridium/iridiumskyblock/schematics/SchematicAsync.java b/src/main/java/com/iridium/iridiumskyblock/schematics/SchematicAsync.java index 22b50758e..3ff669776 100644 --- a/src/main/java/com/iridium/iridiumskyblock/schematics/SchematicAsync.java +++ b/src/main/java/com/iridium/iridiumskyblock/schematics/SchematicAsync.java @@ -5,9 +5,11 @@ import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.Chest; import org.bukkit.block.data.BlockData; +import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.scheduler.BukkitRunnable; import org.jnbt.*; @@ -146,4 +148,139 @@ public void clearCache() { schematicCache.clear(); } + // TODO: should consider chunk-batching like w/ WE + public void deleteIsland(World world, Location pos1, Location pos2, int minHeight, int maxHeight, CompletableFuture completableFuture, int delay) { + + Set modifiedChunks = new HashSet<>(); + + int minX = Math.min(pos1.getBlockX(), pos2.getBlockX()); + int maxX = Math.max(pos1.getBlockX(), pos2.getBlockX()); + int minZ = Math.min(pos1.getBlockZ(), pos2.getBlockZ()); + int maxZ = Math.max(pos1.getBlockZ(), pos2.getBlockZ()); + + new BukkitRunnable() { + + int y = maxHeight; + int x = minX; + int z = minZ; + + @Override + public void run() { + for (int i = 0; i < IridiumSkyblock.getInstance().getConfiguration().pasterLimitPerTick; i++) { + + Block block = world.getBlockAt(x, y, z); + + while (block.isEmpty()) { + if(increment()) return; + block = world.getBlockAt(x, y, z); + } + + if (block.getState() instanceof InventoryHolder) { + ((InventoryHolder) block.getState()).getInventory().clear(); + } + block.setType(Material.AIR, false); + modifiedChunks.add(((long) (x >> 4) << 32) | ((z >> 4) & 0xFFFFFFFFL)); + + increment(); + } + } + + public boolean increment() { + if (++z > maxZ) { + z = minZ; + if (++x > maxX) { + x = minX; + if (--y < minHeight) { + finish(); + return true; + } + } + } + return false; + } + + private void finish() { + this.cancel(); + + completableFuture.complete(null); + + new BukkitRunnable() { + @Override + public void run() { + if (!IridiumSkyblock.getInstance().getConfiguration().generatorType.isTerrainGenerator()) { + modifiedChunks.forEach(key -> {world.refreshChunk((int) (key >> 32), key.intValue());}); + } + } + }.runTaskAsynchronously(IridiumSkyblock.getInstance()); + } + }.runTaskTimer(IridiumSkyblock.getInstance(), delay, 1L); + } + + // TODO: should consider chunk-batching like w/ WE + public void regenIsland(World world, World regenWorld, Location pos1, Location pos2, Location pos3, Location pos4, int minHeight, int maxHeight, CompletableFuture completableFuture, int delay) { + + // pos3 and pos4 are unused here because worldedit requires a full location for position coordinates. + // here, we just find the same location as pos1 and pos2 in the regen world. + + Set modifiedChunks = new HashSet<>(); + + int minX = Math.min(pos1.getBlockX(), pos2.getBlockX()); + int maxX = Math.max(pos1.getBlockX(), pos2.getBlockX()); + int minZ = Math.min(pos1.getBlockZ(), pos2.getBlockZ()); + int maxZ = Math.max(pos1.getBlockZ(), pos2.getBlockZ()); + + new BukkitRunnable() { + + int y = minHeight; + int x = minX; + int z = minZ; + + @Override + public void run() { + for (int i = 0; i < IridiumSkyblock.getInstance().getConfiguration().pasterLimitPerTick; i++) { + + Block blockA = regenWorld.getBlockAt(x, y, z); + Block blockB = world.getBlockAt(x, y, z); + + while (blockA.isEmpty() || blockA.getBlockData().equals(blockB.getBlockData())) { + if (increment()) return; + blockA = regenWorld.getBlockAt(x, y, z); + blockB = world.getBlockAt(x, y, z); + } + + blockB.setBlockData(blockA.getBlockData(), false); + modifiedChunks.add(((long) (x >> 4) << 32) | ((z >> 4) & 0xFFFFFFFFL)); + + increment(); + } + } + + public boolean increment() { + if (++z > maxZ) { + z = minZ; + if (++x > maxX) { + x = minX; + if (++y > maxHeight) { + finish(); + return true; + } + } + } + return false; + } + + private void finish() { + this.cancel(); + + completableFuture.complete(null); + + new BukkitRunnable() { + @Override + public void run() { + modifiedChunks.forEach(key -> {world.refreshChunk((int) (key >> 32), key.intValue());}); + } + }.runTaskAsynchronously(IridiumSkyblock.getInstance()); + } + }.runTaskTimer(IridiumSkyblock.getInstance(), delay, 1L); + } } diff --git a/src/main/java/com/iridium/iridiumskyblock/schematics/SchematicPaster.java b/src/main/java/com/iridium/iridiumskyblock/schematics/SchematicPaster.java index b3b8daf46..d7a8c5beb 100644 --- a/src/main/java/com/iridium/iridiumskyblock/schematics/SchematicPaster.java +++ b/src/main/java/com/iridium/iridiumskyblock/schematics/SchematicPaster.java @@ -1,13 +1,14 @@ package com.iridium.iridiumskyblock.schematics; import org.bukkit.Location; +import org.bukkit.World; import java.io.File; import java.util.concurrent.CompletableFuture; public interface SchematicPaster { void paste(File file, Location location, Boolean ignoreAirBlock, CompletableFuture completableFuture); - void clearCache(); - + void deleteIsland(World world, Location pos1, Location pos2, int minHeight, int maxHeight, CompletableFuture completableFuture, int delay); + void regenIsland(World world, World regenWorld, Location pos1, Location pos2, Location pos3, Location pos4, int minHeight, int maxHeight, CompletableFuture completableFuture, int delay); } diff --git a/src/main/java/com/iridium/iridiumskyblock/schematics/WorldEdit.java b/src/main/java/com/iridium/iridiumskyblock/schematics/WorldEdit.java index c4f4dcca2..1578fe961 100644 --- a/src/main/java/com/iridium/iridiumskyblock/schematics/WorldEdit.java +++ b/src/main/java/com/iridium/iridiumskyblock/schematics/WorldEdit.java @@ -2,24 +2,36 @@ import com.iridium.iridiumskyblock.IridiumSkyblock; import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.bukkit.BukkitAdapter; import com.sk89q.worldedit.bukkit.BukkitWorld; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.extent.MaskingExtent; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats; import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; +import com.sk89q.worldedit.function.mask.ExistingBlockMask; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.operation.ForwardExtentCopy; import com.sk89q.worldedit.function.operation.Operation; import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.math.BlockVector2; import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.util.SideEffectSet; import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.scheduler.BukkitRunnable; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.util.HashMap; +import java.util.*; import java.util.concurrent.CompletableFuture; public class WorldEdit implements SchematicPaster { @@ -79,4 +91,226 @@ public void paste(File file, Location location, Boolean ignoreAirBlock, Completa public void clearCache() { cachedClipboardFormat.clear(); } + + public void deleteIsland(World world, Location pos1, Location pos2, int minHeight, int maxHeight, CompletableFuture completableFuture, int delay) { + + com.sk89q.worldedit.world.World weWorld = BukkitAdapter.adapt(world); + + try (EditSession editSession = com.sk89q.worldedit.WorldEdit.getInstance().newEditSession(weWorld)) { + BlockVector3 min = BlockVector3.at(pos1.getBlockX(), minHeight, pos1.getBlockZ()); + BlockVector3 max = BlockVector3.at(pos2.getBlockX(), maxHeight, pos2.getBlockZ()); + CuboidRegion selection = new CuboidRegion(min, max); + + editSession.getChangeSet().setRecordChanges(false); + editSession.setReorderMode(EditSession.ReorderMode.FAST); + editSession.setSideEffectApplier(SideEffectSet.none()); + ExistingBlockMask nonAirMask = new ExistingBlockMask(editSession); + editSession.setMask(nonAirMask); + + // grab all points and remove air blocks from the selection + List points = new ArrayList<>(); + for (BlockVector3 point : selection) { + if (nonAirMask.test(point)) { + points.add(point); + } + } + + points = points.reversed(); // style points + + // group points into chunks + Map> chunkBatches = new LinkedHashMap<>(); + for (BlockVector3 pt : points) { + BlockVector2 chunkPos = BlockVector2.at(pt.getX() >> 4, pt.getZ() >> 4); + chunkBatches.computeIfAbsent(chunkPos, k -> new ArrayList<>()).add(pt); + } + + // chunk list + List>> batches = new ArrayList<>(chunkBatches.entrySet()); + + new BukkitRunnable() { + int chunkIndex = 0; + int blockInsideChunkIndex = 0; + final int blocksPerTick = IridiumSkyblock.getInstance().getConfiguration().pasterLimitPerTick; + private final com.sk89q.worldedit.world.block.BlockState air = + com.sk89q.worldedit.world.block.BlockTypes.AIR.getDefaultState(); + + @Override + public void run() { + int processedThisTick = 0; + + while (processedThisTick < blocksPerTick) { + + if (chunkIndex >= batches.size()) { + finish(); + return; + } + + List currentChunkBlocks = batches.get(chunkIndex).getValue(); + + while (blockInsideChunkIndex < currentChunkBlocks.size() && processedThisTick < blocksPerTick) { + BlockVector3 point = currentChunkBlocks.get(blockInsideChunkIndex); + + try { + editSession.setBlock(point, air); + } catch (MaxChangedBlocksException e) { + finish(); + return; + } + + blockInsideChunkIndex++; + processedThisTick++; + } + + if (blockInsideChunkIndex >= currentChunkBlocks.size()) { + chunkIndex++; + blockInsideChunkIndex = 0; + editSession.flushSession(); // deprecated, but this pushes changes to the server, so it's critical for performance + } + } + } + + private void finish() { + this.cancel(); + + completableFuture.complete(null); + + new BukkitRunnable() { + @Override + public void run() { + if (!IridiumSkyblock.getInstance().getConfiguration().generatorType.isTerrainGenerator()) { + weWorld.fixLighting(selection.getChunks()); + } + } + }.runTask(IridiumSkyblock.getInstance()); + } + }.runTaskTimer(IridiumSkyblock.getInstance(), delay, 1L); + } + } + + public void regenIsland(World world, World regenWorld, Location pos1, Location pos2, Location pos3, Location pos4, int minHeight, int maxHeight, CompletableFuture completableFuture, int delay) { + + com.sk89q.worldedit.world.World weWorld = BukkitAdapter.adapt(world); + com.sk89q.worldedit.world.World weRegenWorld = BukkitAdapter.adapt(regenWorld); + + // there's gotta be a more succinct method to do this + BlockVector3 min = BlockVector3.at(pos1.getBlockX(), minHeight, pos1.getBlockZ()); + BlockVector3 max = BlockVector3.at(pos2.getBlockX(), maxHeight, pos2.getBlockZ()); + CuboidRegion selection = new CuboidRegion(min, max); + + BlockVector3 min2 = BlockVector3.at(pos3.getBlockX(), minHeight, pos3.getBlockZ()); + BlockVector3 max2 = BlockVector3.at(pos4.getBlockX(), maxHeight, pos4.getBlockZ()); + CuboidRegion selection2 = new CuboidRegion(min2, max2); + + BlockArrayClipboard clipboard = new BlockArrayClipboard(selection); + + // copy + try (EditSession editSession = com.sk89q.worldedit.WorldEdit.getInstance().newEditSession(weRegenWorld)) { + editSession.getChangeSet().setRecordChanges(false); + editSession.setReorderMode(EditSession.ReorderMode.FAST); + editSession.setSideEffectApplier(SideEffectSet.none()); + ExistingBlockMask nonAirMask = new ExistingBlockMask(editSession); + editSession.setMask(nonAirMask); + + ForwardExtentCopy forwardExtentCopy = new ForwardExtentCopy( + weRegenWorld, + selection, + clipboard, + selection.getMinimumPoint() + ); + + Operations.complete(forwardExtentCopy); + + } catch (WorldEditException e) { + throw new RuntimeException(e); + } + + // paste + EditSession editSession = com.sk89q.worldedit.WorldEdit.getInstance().newEditSession(weWorld); + editSession.getChangeSet().setRecordChanges(false); + editSession.setReorderMode(EditSession.ReorderMode.FAST); + editSession.setSideEffectApplier(SideEffectSet.none()); + + BlockVector3 offset = selection2.getMinimumPoint().subtract(clipboard.getMinimumPoint()); + + Mask clipboardMask = new ExistingBlockMask(clipboard); + MaskingExtent maskedClipboard = new MaskingExtent(clipboard, clipboardMask); + + // grab all points and remove air blocks from the selection + List points = new ArrayList<>(); + for (BlockVector3 point : clipboard.getRegion()) { + if (maskedClipboard.getMask().test(point)) { + points.add(point); + } + } + + // group points into chunks + Map> chunkBatches = new LinkedHashMap<>(); + for (BlockVector3 pt : points) { + BlockVector2 chunkPos = BlockVector2.at(pt.getX() >> 4, pt.getZ() >> 4); + chunkBatches.computeIfAbsent(chunkPos, k -> new ArrayList<>()).add(pt); + } + + // chunk list + List>> batches = new ArrayList<>(chunkBatches.entrySet()); + + new BukkitRunnable() { + int chunkIndex = 0; + int blockInsideChunkIndex = 0; + final int blocksPerTick = IridiumSkyblock.getInstance().getConfiguration().pasterLimitPerTick; + private final com.sk89q.worldedit.world.block.BlockState air = + com.sk89q.worldedit.world.block.BlockTypes.AIR.getDefaultState(); + + @Override + public void run() { + int processedThisTick = 0; + + while (processedThisTick < blocksPerTick) { + + if (chunkIndex >= batches.size()) { + finish(); + return; + } + + List currentChunkBlocks = batches.get(chunkIndex).getValue(); + + while (blockInsideChunkIndex < currentChunkBlocks.size() && processedThisTick < blocksPerTick) { + + BlockVector3 point = currentChunkBlocks.get(blockInsideChunkIndex); + com.sk89q.worldedit.world.block.BlockState block = clipboard.getBlock(point); + + try { + editSession.setBlock(point.add(offset), block); + } catch (MaxChangedBlocksException e) { + finish(); + return; + } + + blockInsideChunkIndex++; + processedThisTick++; + } + + if (blockInsideChunkIndex >= currentChunkBlocks.size()) { + chunkIndex++; + blockInsideChunkIndex = 0; + editSession.flushSession(); // style points + } + } + } + + private void finish() { + this.cancel(); + + completableFuture.complete(null); + + new BukkitRunnable() { + @Override + public void run() { + if (!IridiumSkyblock.getInstance().getConfiguration().generatorType.isTerrainGenerator()) { + weWorld.fixLighting(selection.getChunks()); + } + } + }.runTask(IridiumSkyblock.getInstance()); + } + }.runTaskTimer(IridiumSkyblock.getInstance(), delay, 1L); + } }