From 7ae2d65607c9991d6fd7a8972d5fe3f605710e4d Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Fri, 16 Jun 2017 15:28:10 +1000 Subject: [PATCH] Various Dynamic chunk rendering (experimental section in config) Use local files for CFI heightmaps: file:// - Root directory is plugins/FastAsyncWorldEdit/heightmap Optimize anvil for 1.12 Add safety checks to anvil commands Move anvil command implementation to com.boydti.fawe.jnbt.anvil.filters Add anvil chunk delete Add MCAWriter mca offset method Fix count -d Fix taskbuilder split task concurrency issue --- build.gradle | 1 + bukkit/build.gradle | 1 + .../com/boydti/fawe/bukkit/FaweBukkit.java | 30 +- .../boydti/fawe/bukkit/RenderListener.java | 135 +++++ .../fawe/bukkit/v1_10/BukkitQueue_1_10.java | 2 + .../fawe/bukkit/v1_11/BukkitQueue_1_11.java | 2 + .../fawe/bukkit/v1_12/BukkitQueue_1_12.java | 215 ++++++-- .../fawe/bukkit/v1_7/BukkitQueue17.java | 74 +++ .../fawe/bukkit/v1_8/BukkitQueue18R3.java | 75 +++ .../fawe/bukkit/v1_9/BukkitQueue_1_9_R1.java | 2 + .../fawe/bukkit/wrapper/AsyncChunk.java | 5 + .../fawe/bukkit/wrapper/AsyncWorld.java | 108 ++++ .../main/java/com/boydti/fawe/FaweCache.java | 10 + .../boydti/fawe/command/AnvilCommands.java | 492 +++++++----------- .../main/java/com/boydti/fawe/config/BBC.java | 2 + .../java/com/boydti/fawe/config/Settings.java | 16 +- .../com/boydti/fawe/jnbt/NBTStreamer.java | 10 + .../jnbt/anvil/HeightMapMCAGenerator.java | 48 +- .../com/boydti/fawe/jnbt/anvil/MCAFile.java | 9 + .../com/boydti/fawe/jnbt/anvil/MCAWriter.java | 38 +- .../fawe/jnbt/anvil/filters/CountFilter.java | 50 ++ .../jnbt/anvil/filters/CountIdFilter.java | 38 ++ .../filters/DeleteUninhabitedFilter.java | 92 ++++ .../filters/MappedReplacePatternFilter.java | 71 +++ .../jnbt/anvil/filters/RemoveLayerFilter.java | 50 ++ .../anvil/filters/ReplacePatternFilter.java | 31 ++ .../anvil/filters/ReplaceSimpleFilter.java | 23 + .../collection/LocalBlockVector2DSet.java | 5 - .../fawe/object/collection/PrimitiveList.java | 23 + .../regions/general/plot/CreateFromImage.java | 17 +- .../boydti/fawe/util/task/TaskBuilder.java | 29 +- .../worldedit/command/BrushCommands.java | 2 +- .../worldedit/command/SelectionCommands.java | 3 +- .../extension/platform/CommandManager.java | 23 +- 34 files changed, 1287 insertions(+), 445 deletions(-) create mode 100644 bukkit/src/main/java/com/boydti/fawe/bukkit/RenderListener.java create mode 100644 core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/CountFilter.java create mode 100644 core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/CountIdFilter.java create mode 100644 core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/DeleteUninhabitedFilter.java create mode 100644 core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/MappedReplacePatternFilter.java create mode 100644 core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/RemoveLayerFilter.java create mode 100644 core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/ReplacePatternFilter.java create mode 100644 core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/ReplaceSimpleFilter.java diff --git a/build.gradle b/build.gradle index 43088309..e897bfe2 100644 --- a/build.gradle +++ b/build.gradle @@ -79,6 +79,7 @@ subprojects { repositories { mavenCentral() + maven {url "https://repo.destroystokyo.com/repository/maven-public//"} maven { url = "https://mvnrepository.com/artifact/"} maven {url "http://ci.emc.gs/nexus/content/groups/aikar/" } maven {url "http://ci.mengcraft.com:8080/plugin/repository/everything/"} diff --git a/bukkit/build.gradle b/bukkit/build.gradle index e3f0a388..9e65c028 100644 --- a/bukkit/build.gradle +++ b/bukkit/build.gradle @@ -2,6 +2,7 @@ repositories { flatDir {dirs 'lib'} } dependencies { + compile 'com.destroystokyo.paper:paper-api:1.11.2-R0.1-SNAPSHOT' compile project(':core') compile 'org.bukkit.craftbukkitv1_10:craftbukkitv1_10:1.10' compile 'org.bukkit.craftbukkitv1_11:Craftbukkit:1.11' diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java index 15b50de2..245e894c 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/FaweBukkit.java @@ -21,7 +21,6 @@ import com.boydti.fawe.object.FaweQueue; import com.boydti.fawe.regions.FaweMaskManager; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.ReflectionUtils; -import com.boydti.fawe.util.SetQueue; import com.boydti.fawe.util.TaskManager; import com.sk89q.worldedit.bukkit.EditSessionBlockChangeDelegate; import com.sk89q.worldedit.bukkit.WorldEditPlugin; @@ -44,9 +43,6 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.world.ChunkLoadEvent; -import org.bukkit.event.world.ChunkPopulateEvent; -import org.bukkit.event.world.ChunkUnloadEvent; import org.bukkit.plugin.Plugin; import org.primesoft.blockshub.BlocksHubBukkit; @@ -87,6 +83,9 @@ public class FaweBukkit implements IFawe, Listener { debug(" - This is only a recommendation"); debug("=============================="); } + if (Bukkit.getVersion().contains("git-Paper") && Settings.IMP.EXPERIMENTAL.DYNAMIC_CHUNK_RENDERING) { + new RenderListener(plugin); + } } catch (final Throwable e) { MainUtil.handleError(e); Bukkit.getServer().shutdown(); @@ -411,29 +410,6 @@ public class FaweBukkit implements IFawe, Listener { // } // } - private boolean runChunkLoad = false; - - @EventHandler - public void onChunkLoad(ChunkLoadEvent event) { - if (runChunkLoad) return; - try { - runChunkLoad = true; - SetQueue.IMP.runMiscTasks(); - } finally { - runChunkLoad = false; - } - } - - @EventHandler - public void onChunkUnload(ChunkUnloadEvent event) { - SetQueue.IMP.runMiscTasks(); - } - - @EventHandler - public void onChunkPopulate(ChunkPopulateEvent event) { - SetQueue.IMP.runMiscTasks(); - } - @EventHandler(priority = EventPriority.MONITOR) public void onPlayerQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/RenderListener.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/RenderListener.java new file mode 100644 index 00000000..09556029 --- /dev/null +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/RenderListener.java @@ -0,0 +1,135 @@ +package com.boydti.fawe.bukkit; + +import com.boydti.fawe.Fawe; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.util.TaskManager; +import java.util.Iterator; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.plugin.Plugin; + +public class RenderListener implements Listener { + + private final Map views = new ConcurrentHashMap<>(); + private Iterator> entrySet; + private final long GLOBAL_START = System.currentTimeMillis(); + private int OFFSET = 6; + + public RenderListener(Plugin plugin) { + Bukkit.getPluginManager().registerEvents(this, plugin); + TaskManager.IMP.repeat(new Runnable() { + private long last = 0; + + @Override + public void run() { + if (views.isEmpty()) return; + + long now = System.currentTimeMillis(); + int tps32 = (int) (Math.round(Fawe.get().getTimer().getTPS()) * 32); + long diff = now - last; + last = now; + if (diff > 75) { + OFFSET = diff > 100 ? 0 : 4; + return; + } + int timeOut; + if (diff < 55 && tps32 > 608) { + OFFSET = 8; + timeOut = 2; + } else { + int tpsSqr = tps32 * tps32; + OFFSET = 1 + (tps32 / 102400); + timeOut = 162 - (tps32 / 2560); + } + if (entrySet == null || !entrySet.hasNext()) { + entrySet = views.entrySet().iterator(); + } + int nowTick = (int) (Fawe.get().getTimer().getTick()); + while (entrySet.hasNext()) { + Map.Entry entry = entrySet.next(); + int[] value = entry.getValue(); + if (nowTick - value[1] >= timeOut) { + value[1] = nowTick + 1; + Player player = Bukkit.getPlayer(entry.getKey()); + setViewDistance(player, Math.max(4, value[0] + 1)); + long spent = System.currentTimeMillis() - now; + if (spent > 5) { + if (spent > 10) + value[1] = nowTick + 20; + return; + } + } + } + } + }, 1); + } + + public void setViewDistance(Player player, int value) { + UUID uuid = player.getUniqueId(); + if (value == 10) { + views.remove(uuid); + } else { + int[] val = views.get(uuid); + if (val == null) { + val = new int[] {value, (int) Fawe.get().getTimer().getTick()}; + UUID uid = player.getUniqueId(); + views.put(uid, val); + } else { + if (value <= val[0]) { + val[1] = (int) Fawe.get().getTimer().getTick(); + } + if (val[0] == value) { + return; + } else { + val[0] = value; + } + } + } + player.setViewDistance(value); + } + + public int getViewDistance(Player player) { + int[] value = views.get(player.getUniqueId()); + return value == null ? 10 : value[0]; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerTeleport(PlayerTeleportEvent event) { + setViewDistance(event.getPlayer(), 1); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerMove(PlayerMoveEvent event) { + Location from = event.getFrom(); + Location to = event.getTo(); + if (from.getBlockX() >> OFFSET != to.getBlockX() >> OFFSET || from.getBlockZ() >> OFFSET != to.getBlockZ() >> OFFSET) { + Player player = event.getPlayer(); + int currentView = getViewDistance(player); + setViewDistance(player, Math.max(currentView - 1, 1)); + } + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onPlayerJoin(org.bukkit.event.player.PlayerJoinEvent event) { + Player player = event.getPlayer(); + setViewDistance(player, 1); + FawePlayer fp = FawePlayer.wrap(player); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerLeave(org.bukkit.event.player.PlayerQuitEvent event) { + Player player = event.getPlayer(); + UUID uid = player.getUniqueId(); + views.remove(uid); + FawePlayer fp = FawePlayer.wrap(player); + } +} diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_10/BukkitQueue_1_10.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_10/BukkitQueue_1_10.java index d16ef791..44fa718a 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_10/BukkitQueue_1_10.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_10/BukkitQueue_1_10.java @@ -39,6 +39,7 @@ import net.minecraft.server.v1_10_R1.BiomeBase; import net.minecraft.server.v1_10_R1.BiomeCache; import net.minecraft.server.v1_10_R1.Block; import net.minecraft.server.v1_10_R1.BlockPosition; +import net.minecraft.server.v1_10_R1.ChunkCoordIntPair; import net.minecraft.server.v1_10_R1.ChunkProviderGenerate; import net.minecraft.server.v1_10_R1.ChunkProviderServer; import net.minecraft.server.v1_10_R1.ChunkSection; @@ -457,6 +458,7 @@ public class BukkitQueue_1_10 extends BukkitQueue_0 chunks) { + ArrayDeque queue = TaskManager.IMP.sync(new RunnableVal>() { + @Override + public void run(ArrayDeque value) { + this.value = new ArrayDeque<>(); + ChunkProviderServer provider = nmsWorld.getChunkProviderServer(); + int bcx = (allowed.minX >> 9) << 5; + int bcz = (allowed.minZ >> 9) << 5; + int tcx = 31 + (allowed.maxX >> 9) << 5; + int tcz = 31 + (allowed.maxZ >> 9) << 5; + Iterator iter = provider.a().iterator(); + while (iter.hasNext()) { + net.minecraft.server.v1_12_R1.Chunk chunk = iter.next(); + int cx = chunk.locX; + int cz = chunk.locZ; + if (cx >= bcx && cx <= tcx && cz >= bcz && cz <= tcz) { + this.value.add(chunk); + if (getPlayerChunk(nmsWorld, cx, cz) != null) { + chunks.add(ChunkCoordIntPair.a(chunk.locX, chunk.locZ)); + } + } + } + } + }); + if (Fawe.get().getMainThread() == Thread.currentThread()) { + ChunkProviderServer provider = nmsWorld.getChunkProviderServer(); + for (net.minecraft.server.v1_12_R1.Chunk chunk : queue) { + provider.unloadChunk(chunk, true); + } + World world = getWorld(); + boolean autoSave = world.isAutoSave(); + world.setAutoSave(true); + for (int i = 0; i < 50 && !provider.getName().endsWith(" 0"); i++) { + provider.unloadChunks(); + } + world.setAutoSave(autoSave); + } else { + new TaskBuilder().syncWhenFree(new TaskBuilder.SplitTask(50) { + @Override + public Object exec(Object previous) { + ChunkProviderServer provider = nmsWorld.getChunkProviderServer(); + for (net.minecraft.server.v1_12_R1.Chunk chunk : queue) { + provider.unloadChunk(chunk, true); + split(); + } + try { + IChunkLoader loader = (IChunkLoader) fieldChunkLoader.get(provider); + if (loader instanceof ChunkRegionLoader) { + ChunkRegionLoader crl = (ChunkRegionLoader) loader; + for (int i = 0; i < queue.size() && crl.a(); i++) { + split(); + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + }).build(); + } + } + @Override public boolean setMCA(Runnable whileLocked, final RegionWrapper allowed, boolean unload) { try { - TaskManager.IMP.sync(new RunnableVal() { + Object lock = new Object(); + ForkJoinPool pool = new ForkJoinPool(); + PrimitiveList chunks = new PrimitiveList(Long.class); + if (unload && Fawe.get().getMainThread() != Thread.currentThread()) unload(allowed, chunks); + Thread operation = new Thread(new Runnable() { @Override - public void run(Object value) { + public void run() { try { + synchronized (lock) { + lock.wait(); + } synchronized (RegionFileCache.class) { - ArrayDeque chunks = new ArrayDeque<>(); - World world = getWorld(); - world.setKeepSpawnInMemory(false); - ChunkProviderServer provider = nmsWorld.getChunkProviderServer(); - if (unload) { // Unload chunks - int bcx = (allowed.minX >> 9) << 5; - int bcz = (allowed.minZ >> 9) << 5; - int tcx = 31 + (allowed.maxX >> 9) << 5; - int tcz = 31 + (allowed.maxZ >> 9) << 5; - Iterator iter = provider.a().iterator(); - while (iter.hasNext()) { - net.minecraft.server.v1_12_R1.Chunk chunk = iter.next(); - int cx = chunk.locX; - int cz = chunk.locZ; - if (cx >= bcx && cx <= tcx && cz >= bcz && cz <= tcz) { - chunks.add(chunk); - } - } - for (net.minecraft.server.v1_12_R1.Chunk chunk : chunks) { - provider.unloadChunk(chunk, true); - } - } - provider.c(); - - if (unload) { // Unload regions - Map map = RegionFileCache.a; - Iterator> iter = map.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry entry = iter.next(); - RegionFile regionFile = entry.getValue(); - regionFile.c(); - iter.remove(); - } - } whileLocked.run(); - // Load the chunks again - if (unload) { - for (net.minecraft.server.v1_12_R1.Chunk chunk : chunks) { - chunk = provider.loadChunk(chunk.locX, chunk.locZ); - if (chunk != null) { - sendChunk(chunk, 0); - } - } - } - } } catch (Throwable e) { e.printStackTrace(); } } }); + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + try { + synchronized (RegionFileCache.class) { + operation.start(); // Async + ChunkProviderServer provider = nmsWorld.getChunkProviderServer(); + if (unload) unload(allowed, chunks); + provider.c(); + if (unload) { // Unload regions + Map map = RegionFileCache.a; + Iterator> iter = map.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + RegionFile regionFile = entry.getValue(); + pool.submit(new Runnable() { + @Override + public void run() { + try { + regionFile.c(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + iter.remove(); + } + pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + } + synchronized (lock) { + lock.notifyAll(); + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + }); + operation.join(); + + new TaskBuilder().syncWhenFree(new TaskBuilder.SplitTask(5) { + @Override + public Object exec(Object previous) { + ChunkProviderServer provider = nmsWorld.getChunkProviderServer(); + for (long pos : chunks) { + int x = (int) pos; + int z = (int) (pos >> 32); + net.minecraft.server.v1_12_R1.Chunk chunk = provider.getOrLoadChunkAt(x, z); + if (chunk != null) { + PlayerChunk pc = getPlayerChunk(nmsWorld, x, z); + sendChunk(pc, chunk, 0); + } + split(); + } + return null; + } + }).buildAsync(); return true; } catch (Throwable e) { e.printStackTrace(); @@ -474,7 +570,7 @@ public class BukkitQueue_1_12 extends BukkitQueue_0() { + @Override + public void run(Object value) { + try { + synchronized (RegionFileCache.class) { + ArrayDeque chunks = new ArrayDeque<>(); + World world = getWorld(); + world.setKeepSpawnInMemory(false); + ChunkProviderServer provider = nmsWorld.chunkProviderServer; + if (unload) { // Unload chunks + boolean autoSave = world.isAutoSave(); + world.setAutoSave(true); + int bcx = (allowed.minX >> 9) << 5; + int bcz = (allowed.minZ >> 9) << 5; + int tcx = 31 + (allowed.maxX >> 9) << 5; + int tcz = 31 + (allowed.maxZ >> 9) << 5; + Iterator iter = provider.a().iterator(); + while (iter.hasNext()) { + Chunk chunk = iter.next(); + int cx = chunk.locX; + int cz = chunk.locZ; + if (cx >= bcx && cx <= tcx && cz >= bcz && cz <= tcz) { + provider.unloadQueue.add(ChunkCoordIntPair.a(chunk.locX, chunk.locZ)); + } + } + for (int i = 0; i < 50 && !provider.getName().endsWith(" 0"); i++) provider.unloadChunks(); + world.setAutoSave(autoSave); + } + provider.c(); + + if (unload) { // Unload regions + Map map = RegionFileCache.a; + Iterator> iter = map.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + RegionFile regionFile = entry.getValue(); + regionFile.c(); + iter.remove(); + } + } + whileLocked.run(); + // Load the chunks again + if (unload) { + for (Chunk chunk : chunks) { + chunk = provider.loadChunk(chunk.locX, chunk.locZ); + if (chunk != null) { + provider.chunks.put(ChunkCoordIntPair.a(chunk.locX, chunk.locZ), chunk); + sendChunk(chunk, 0); + } + } + } + + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + }); + return true; + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + @Override public boolean regenerateChunk(World world, int x, int z, BaseBiome biome, Long seed) { if (biome != null) { diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue18R3.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue18R3.java index dff17cc5..26fef159 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue18R3.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_8/BukkitQueue18R3.java @@ -4,6 +4,7 @@ import com.boydti.fawe.FaweCache; import com.boydti.fawe.bukkit.v0.BukkitQueue_0; import com.boydti.fawe.example.CharFaweChunk; import com.boydti.fawe.object.FaweChunk; +import com.boydti.fawe.object.RegionWrapper; import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.MathMan; @@ -15,9 +16,11 @@ import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.world.biome.BaseBiome; import java.io.File; import java.lang.reflect.Field; +import java.util.ArrayDeque; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -25,6 +28,8 @@ import java.util.UUID; import net.minecraft.server.v1_8_R3.Block; import net.minecraft.server.v1_8_R3.BlockPosition; import net.minecraft.server.v1_8_R3.Chunk; +import net.minecraft.server.v1_8_R3.ChunkCoordIntPair; +import net.minecraft.server.v1_8_R3.ChunkProviderServer; import net.minecraft.server.v1_8_R3.ChunkSection; import net.minecraft.server.v1_8_R3.Entity; import net.minecraft.server.v1_8_R3.EntityPlayer; @@ -40,6 +45,8 @@ import net.minecraft.server.v1_8_R3.NibbleArray; import net.minecraft.server.v1_8_R3.Packet; import net.minecraft.server.v1_8_R3.PacketPlayOutMapChunk; import net.minecraft.server.v1_8_R3.PlayerChunkMap; +import net.minecraft.server.v1_8_R3.RegionFile; +import net.minecraft.server.v1_8_R3.RegionFileCache; import net.minecraft.server.v1_8_R3.ServerNBTManager; import net.minecraft.server.v1_8_R3.TileEntity; import net.minecraft.server.v1_8_R3.WorldData; @@ -124,6 +131,74 @@ public class BukkitQueue18R3 extends BukkitQueue_0() { + @Override + public void run(Object value) { + try { + synchronized (RegionFileCache.class) { + ArrayDeque chunks = new ArrayDeque<>(); + World world = getWorld(); + world.setKeepSpawnInMemory(false); + ChunkProviderServer provider = nmsWorld.chunkProviderServer; + if (unload) { // Unload chunks + boolean autoSave = world.isAutoSave(); + world.setAutoSave(true); + int bcx = (allowed.minX >> 9) << 5; + int bcz = (allowed.minZ >> 9) << 5; + int tcx = 31 + (allowed.maxX >> 9) << 5; + int tcz = 31 + (allowed.maxZ >> 9) << 5; + Iterator iter = provider.a().iterator(); + while (iter.hasNext()) { + Chunk chunk = iter.next(); + int cx = chunk.locX; + int cz = chunk.locZ; + if (cx >= bcx && cx <= tcx && cz >= bcz && cz <= tcz) { + provider.unloadQueue.add(ChunkCoordIntPair.a(chunk.locX, chunk.locZ)); + } + } + for (int i = 0; i < 50 && !provider.getName().endsWith(" 0"); i++) provider.unloadChunks(); + world.setAutoSave(autoSave); + } + provider.c(); + + if (unload) { // Unload regions + Map map = RegionFileCache.a; + Iterator> iter = map.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + RegionFile regionFile = entry.getValue(); + regionFile.c(); + iter.remove(); + } + } + whileLocked.run(); + // Load the chunks again + if (unload) { + for (Chunk chunk : chunks) { + chunk = provider.loadChunk(chunk.locX, chunk.locZ); + if (chunk != null) { + provider.chunks.put(ChunkCoordIntPair.a(chunk.locX, chunk.locZ), chunk); + sendChunk(chunk, 0); + } + } + } + + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + }); + return true; + } catch (Throwable e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + @Override public boolean regenerateChunk(World world, int x, int z, BaseBiome biome, Long seed) { if (biome != null) { diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9_R1.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9_R1.java index 8a2c01c0..b1c17ecf 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9_R1.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_9/BukkitQueue_1_9_R1.java @@ -37,6 +37,7 @@ import java.util.Set; import java.util.UUID; import net.minecraft.server.v1_9_R2.Block; import net.minecraft.server.v1_9_R2.BlockPosition; +import net.minecraft.server.v1_9_R2.ChunkCoordIntPair; import net.minecraft.server.v1_9_R2.ChunkProviderServer; import net.minecraft.server.v1_9_R2.ChunkSection; import net.minecraft.server.v1_9_R2.DataBits; @@ -318,6 +319,7 @@ public class BukkitQueue_1_9_R1 extends BukkitQueue_0() { + @Override + public void run(Integer value) { + this.value = parent.getEntityCount(); + } + }); + } + + @Override + public int getTileEntityCount() { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Integer value) { + this.value = parent.getTileEntityCount(); + } + }); + } + + @Override + public int getTickableTileEntityCount() { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Integer value) { + this.value = parent.getTickableTileEntityCount(); + } + }); + } + + @Override + public int getChunkCount() { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Integer value) { + this.value = parent.getChunkCount(); + } + }); + } + + @Override + public int getPlayerCount() { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Integer value) { + this.value = parent.getPlayerCount(); + } + }); + } + @Override public Block getBlockAt(final int x, final int y, final int z) { return new AsyncBlock(this, queue, x, y, z); @@ -300,6 +353,21 @@ public class AsyncWorld extends DelegateFaweQueue implements World, HasFaweQueue return getChunkAt(block.getX(), block.getZ()); } + @Override + public void getChunkAtAsync(int x, int z, ChunkLoadCallback cb) { + parent.getChunkAtAsync(x, z, cb); + } + + @Override + public void getChunkAtAsync(Location location, ChunkLoadCallback cb) { + parent.getChunkAtAsync(location, cb); + } + + @Override + public void getChunkAtAsync(Block block, ChunkLoadCallback cb) { + parent.getChunkAtAsync(block, cb); + } + @Override public boolean isChunkLoaded(Chunk chunk) { return chunk.isLoaded(); @@ -746,6 +814,26 @@ public class AsyncWorld extends DelegateFaweQueue implements World, HasFaweQueue }); } + @Override + public T spawn(Location location, Class clazz, Consumer function) throws IllegalArgumentException { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(T value) { + this.value = parent.spawn(location, clazz, function); + } + }); + } + + @Override + public FallingBlock spawnFallingBlock(Location location, MaterialData data) throws IllegalArgumentException { + return TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(FallingBlock value) { + this.value = parent.spawnFallingBlock(location, data); + } + }); + } + @Override @Deprecated public FallingBlock spawnFallingBlock(Location location, Material material, byte data) throws IllegalArgumentException { @@ -979,6 +1067,26 @@ public class AsyncWorld extends DelegateFaweQueue implements World, HasFaweQueue }); } + @Override + public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch) { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.playSound(location, sound, category, volume, pitch); + } + }); + } + + @Override + public void playSound(Location location, String sound, SoundCategory category, float volume, float pitch) { + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + parent.playSound(location, sound, category, volume, pitch); + } + }); + } + @Override public String[] getGameRules() { return parent.getGameRules(); diff --git a/core/src/main/java/com/boydti/fawe/FaweCache.java b/core/src/main/java/com/boydti/fawe/FaweCache.java index 12a16cd9..bb2b71cb 100644 --- a/core/src/main/java/com/boydti/fawe/FaweCache.java +++ b/core/src/main/java/com/boydti/fawe/FaweCache.java @@ -14,12 +14,15 @@ import com.sk89q.jnbt.LongTag; import com.sk89q.jnbt.ShortTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; +import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.blocks.BaseItem; import com.sk89q.worldedit.blocks.BlockType; import com.sk89q.worldedit.blocks.ImmutableBlock; import com.sk89q.worldedit.blocks.ImmutableDatalessBlock; import com.sk89q.worldedit.blocks.ImmutableNBTBlock; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.input.ParserContext; import com.sk89q.worldedit.world.biome.BaseBiome; import com.sk89q.worldedit.world.registry.BundledBlockData; import java.awt.Color; @@ -118,6 +121,13 @@ public class FaweCache { return getCombined(block.getId(), block.getData()); } + public static final BaseBlock getBlock(String block, boolean allAllowed, boolean allowNoData) throws InputParseException { + ParserContext context = new ParserContext(); + context.setRestricted(!allAllowed); + context.setPreferringWildcard(allowNoData); + return WorldEdit.getInstance().getBlockFactory().parseFromInput(block, context); + } + public static final Color getColor(int id, int data) { Color exact = CACHE_COLOR[getCombined(id, data)]; if (exact != null) { diff --git a/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java b/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java index d5ba7c26..7c955319 100644 --- a/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java +++ b/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java @@ -1,31 +1,32 @@ package com.boydti.fawe.command; import com.boydti.fawe.Fawe; +import com.boydti.fawe.FaweAPI; import com.boydti.fawe.FaweCache; import com.boydti.fawe.config.BBC; -import com.boydti.fawe.jnbt.NBTStreamer; -import com.boydti.fawe.jnbt.anvil.MCAChunk; import com.boydti.fawe.jnbt.anvil.MCAClipboard; import com.boydti.fawe.jnbt.anvil.MCAFile; import com.boydti.fawe.jnbt.anvil.MCAFilter; import com.boydti.fawe.jnbt.anvil.MCAFilterCounter; import com.boydti.fawe.jnbt.anvil.MCAQueue; +import com.boydti.fawe.jnbt.anvil.filters.CountFilter; +import com.boydti.fawe.jnbt.anvil.filters.CountIdFilter; +import com.boydti.fawe.jnbt.anvil.filters.DeleteUninhabitedFilter; +import com.boydti.fawe.jnbt.anvil.filters.MappedReplacePatternFilter; +import com.boydti.fawe.jnbt.anvil.filters.RemoveLayerFilter; +import com.boydti.fawe.jnbt.anvil.filters.ReplacePatternFilter; +import com.boydti.fawe.jnbt.anvil.filters.ReplaceSimpleFilter; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.FaweQueue; import com.boydti.fawe.object.RegionWrapper; -import com.boydti.fawe.object.RunnableVal; -import com.boydti.fawe.object.RunnableVal2; import com.boydti.fawe.object.RunnableVal4; import com.boydti.fawe.object.mask.FaweBlockMatcher; -import com.boydti.fawe.object.number.MutableLong; -import com.boydti.fawe.util.ArrayUtil; import com.boydti.fawe.util.SetQueue; import com.boydti.fawe.util.StringMan; import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.LocalSession; -import com.sk89q.worldedit.MutableBlockVector; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; @@ -41,15 +42,12 @@ import com.sk89q.worldedit.util.command.binding.Switch; import com.sk89q.worldedit.util.command.parametric.Optional; import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.attribute.BasicFileAttributes; +import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkNotNull; @@ -69,16 +67,70 @@ public class AnvilCommands { this.worldEdit = worldEdit; } + /** + * Run safely on an unloaded world (no selection) + * @param player + * @param folder + * @param filter + * @param + * @param + * @return + */ + public static > T runWithWorld(Player player, String folder, T filter) { + if (FaweAPI.getWorld(folder) != null) { + BBC.WORLD_IS_LOADED.send(player); + return null; + } + FaweQueue defaultQueue = SetQueue.IMP.getNewQueue(folder, true, false); + MCAQueue queue = new MCAQueue(folder, defaultQueue.getSaveFolder(), defaultQueue.hasSky()); + return queue.filterWorld(filter); + } + + /** + * Run safely on an existing world within a selection + * @param player + * @param editSession + * @param selection + * @param filter + * @param + * @param + * @return + */ + public static > T runWithSelection(Player player, EditSession editSession, Region selection, T filter) { + if (!(selection instanceof CuboidRegion)) { + BBC.NO_REGION.send(player); + return null; + } + CuboidRegion cuboid = (CuboidRegion) selection; + RegionWrapper wrappedRegion = new RegionWrapper(cuboid.getMinimumPoint(), cuboid.getMaximumPoint()); + String worldName = Fawe.imp().getWorldName(editSession.getWorld()); + FaweQueue tmp = SetQueue.IMP.getNewQueue(worldName, true, false); + File folder = tmp.getSaveFolder(); + MCAQueue queue = new MCAQueue(worldName, folder, tmp.hasSky()); + player.print(BBC.getPrefix() + "Safely unloading regions..."); + tmp.setMCA(new Runnable() { + @Override + public void run() { + player.print(BBC.getPrefix() + "Performing operation..."); + queue.filterRegion(filter, wrappedRegion); + player.print(BBC.getPrefix() + "Safely loading regions..."); + } + }, wrappedRegion, true); + return filter; + } + @Command( aliases = {"replaceall", "rea", "repall"}, usage = " [from-block] ", desc = "Replace all blocks in the selection with another", - flags = "d", + help = "Replace all blocks in the selection with another\n" + + "The -d flag disabled wildcard data matching\n", + flags = "df", min = 2, max = 4 ) @CommandPermissions("worldedit.anvil.replaceall") - public void replaceAll(Player player, EditSession editSession, String folder, @Optional String from, String to, @Switch('d') boolean useData) throws WorldEditException { + public void replaceAll(Player player, String folder, @Optional String from, String to, @Switch('d') boolean useData) throws WorldEditException { final FaweBlockMatcher matchFrom; if (from == null) { matchFrom = FaweBlockMatcher.NOT_AIR; @@ -89,15 +141,9 @@ public class AnvilCommands { matchFrom = FaweBlockMatcher.fromBlocks(worldEdit.getBlocks(player, from, true), useData); } final FaweBlockMatcher matchTo = FaweBlockMatcher.setBlocks(worldEdit.getBlocks(player, to, true)); - File root = new File(folder + File.separator + "region"); - MCAQueue queue = new MCAQueue(folder, root, true); - MCAFilterCounter counter = queue.filterWorld(new MCAFilterCounter() { - @Override - public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong ignore) { - if (matchFrom.apply(block)) matchTo.apply(block); - } - }); - player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(counter.getTotal())); + ReplaceSimpleFilter filter = new ReplaceSimpleFilter(matchFrom, matchTo); + ReplaceSimpleFilter result = runWithWorld(player, folder, filter); + if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); } @Command( @@ -108,75 +154,10 @@ public class AnvilCommands { max = 3 ) @CommandPermissions("worldedit.anvil.deleteallold") - public void deleteAllOld(Player player, String folder, int inhabitedTicks, @Optional("60000") int fileAgeMillis) throws WorldEditException { - FaweQueue defaultQueue = SetQueue.IMP.getNewQueue(folder, true, false); - MCAQueue queue = new MCAQueue(folder, defaultQueue.getSaveFolder(), defaultQueue.hasSky()); - MCAFilterCounter result = queue.filterWorld(new MCAFilterCounter() { - @Override - public MCAFile applyFile(MCAFile mca) { - File file = mca.getFile(); - try { - BasicFileAttributes attr = Files.readAttributes(file.toPath(), BasicFileAttributes.class); - long creation = attr.creationTime().toMillis(); - long modified = attr.lastModifiedTime().toMillis(); - if (modified - creation < fileAgeMillis && modified > creation) { - mca.setDeleted(true); - get().add(512 * 512 * 256); - return null; - } - } catch (IOException | UnsupportedOperationException ignore) {} - try { - ForkJoinPool pool = new ForkJoinPool(); - mca.init(); - mca.forEachSortedChunk(new RunnableVal4() { - @Override - public void run(Integer x, Integer z, Integer offset, Integer size) { - try { - byte[] bytes = mca.getChunkCompressedBytes(offset); - if (bytes == null) return; - Runnable task = new Runnable() { - @Override - public void run() { - try { - mca.streamChunk(offset, new RunnableVal() { - @Override - public void run(NBTStreamer value) { - value.addReader(".Level.InhabitedTime", new RunnableVal2() { - @Override - public void run(Integer index, Long value) { - if (value <= inhabitedTicks) { - MCAChunk chunk = new MCAChunk(queue, x, z); - chunk.setDeleted(true); - synchronized (mca) { - mca.setChunk(chunk); - } - get().add(16 * 16 * 256); - } - } - }); - } - }); - } catch (IOException e) { - e.printStackTrace(); - } - } - }; - pool.submit(task); - } catch (IOException e) { - e.printStackTrace(); - } - } - }); - pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS); - mca.close(pool); - pool.shutdown(); - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - }); - player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); + public void deleteAllOld(Player player, String folder, int inhabitedTicks, @Optional("60000") int fileAgeMillis, @Switch('f') boolean force) throws WorldEditException { + DeleteUninhabitedFilter filter = new DeleteUninhabitedFilter(fileAgeMillis, inhabitedTicks); + DeleteUninhabitedFilter result = runWithWorld(player, folder, filter); + if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); } @Command( @@ -189,62 +170,11 @@ public class AnvilCommands { ) @CommandPermissions("worldedit.anvil.replaceall") public void replaceAllPattern(Player player, String folder, @Optional String from, final Pattern to, @Switch('d') boolean useData, @Switch('m') boolean useMap) throws WorldEditException { - FaweQueue defaultQueue = SetQueue.IMP.getNewQueue(folder, true, false); - MCAQueue queue = new MCAQueue(folder, defaultQueue.getSaveFolder(), defaultQueue.hasSky()); - MCAFilterCounter counter; + MCAFilterCounter filter; if (useMap) { - List split = StringMan.split(from, ','); if (to instanceof RandomPattern) { - Pattern[] patterns = ((RandomPattern) to).getPatterns().toArray(new Pattern[0]); - if (patterns.length == split.size()) { - Pattern[] map = new Pattern[Character.MAX_VALUE + 1]; - for (int i = 0; i < split.size(); i++) { - Pattern pattern = patterns[i]; - String arg = split.get(i); - ArrayList blocks = new ArrayList(); - for (String arg2 : arg.split(",")) { - BaseBlock block = worldEdit.getBlock(player, arg, true); - if (!useData && !arg2.contains(":")) { - block = new BaseBlock(block.getId(), -1); - } - blocks.add(block); - } - for (BaseBlock block : blocks) { - if (block.getData() != -1) { - int combined = FaweCache.getCombined(block); - map[combined] = pattern; - } else { - for (int data = 0; data < 16; data++) { - int combined = FaweCache.getCombined(block.getId(), data); - map[combined] = pattern; - } - } - } - } - - counter = queue.filterWorld(new MCAFilterCounter() { - private final MutableBlockVector mutable = new MutableBlockVector(0, 0, 0); - @Override - public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong ignore) { - int id = block.getId(); - int data = FaweCache.hasData(id) ? block.getData() : 0; - int combined = FaweCache.getCombined(id, data); - Pattern p = map[combined]; - if (p != null) { - BaseBlock newBlock = p.apply(x, y, z); - int currentId = block.getId(); - if (FaweCache.hasNBT(currentId)) { - block.setNbtData(null); - } - block.setId(newBlock.getId()); - block.setData(newBlock.getData()); - } - } - }); - } else { - player.print(BBC.getPrefix() + "Mask:Pattern must be a 1:1 match"); - return; - } + List split = StringMan.split(from, ','); + filter = new MappedReplacePatternFilter(from, (RandomPattern) to, useData); } else { player.print(BBC.getPrefix() + "Must be a pattern list!"); return; @@ -254,27 +184,12 @@ public class AnvilCommands { if (from == null) { matchFrom = FaweBlockMatcher.NOT_AIR; } else { - if (from.contains(":")) { - useData = true; //override d flag, if they specified data they want it - } - matchFrom = FaweBlockMatcher.fromBlocks(worldEdit.getBlocks(player, from, true), useData); + matchFrom = FaweBlockMatcher.fromBlocks(worldEdit.getBlocks(player, from, true), useData || from.contains(":")); } - counter = queue.filterWorld(new MCAFilterCounter() { - @Override - public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong ignore) { - if (matchFrom.apply(block)) { - BaseBlock newBlock = to.apply(x, y, z); - int currentId = block.getId(); - if (FaweCache.hasNBT(currentId)) { - block.setNbtData(null); - } - block.setId(newBlock.getId()); - block.setData(newBlock.getData()); - } - } - }); + filter = new ReplacePatternFilter(matchFrom, to); } - player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(counter.getTotal())); + MCAFilterCounter result = runWithWorld(player, folder, filter); + if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); } @Command( @@ -285,70 +200,96 @@ public class AnvilCommands { min = 2, max = 3 ) - @CommandPermissions("worldedit.anvil.countallstone") + @CommandPermissions("worldedit.anvil.countall") public void countAll(Player player, EditSession editSession, String folder, String arg, @Switch('d') boolean useData) throws WorldEditException { - File root = new File(folder + File.separator + "region"); - MCAQueue queue = new MCAQueue(folder, root, true); - if (arg.contains(":")) { - useData = true; //override d flag, if they specified data they want it - } Set searchBlocks = worldEdit.getBlocks(player, arg, true); - final boolean[] allowedId = new boolean[FaweCache.getId(Character.MAX_VALUE)]; - for (BaseBlock block : searchBlocks) { - allowedId[block.getId()] = true; - } MCAFilterCounter filter; - if (useData) { // Optimize for both cases - final boolean[] allowed = new boolean[Character.MAX_VALUE]; - for (BaseBlock block : searchBlocks) { - allowed[FaweCache.getCombined(block)] = true; - } - filter = new MCAFilterCounter() { - @Override - public MCAChunk applyChunk(MCAChunk chunk, MutableLong count) { - for (int layer = 0; layer < chunk.ids.length; layer++) { - byte[] ids = chunk.ids[layer]; - if (ids == null) { - continue; - } - byte[] datas = chunk.data[layer]; - for (int i = 0; i < ids.length; i++) { - int id = ids[i] & 0xFF; - if (!allowedId[id]) { - continue; - } - int combined = (id) << 4; - if (FaweCache.hasData(id)) { - combined += chunk.getNibble(i, datas); - } - if (allowed[combined]) { - count.increment(); - } - } - } - return null; - } - }; + if (useData || arg.contains(":")) { // Optimize for both cases + CountFilter counter = new CountFilter(); + searchBlocks.forEach(counter::addBlock); + filter = counter; } else { - filter = new MCAFilterCounter() { - @Override - public MCAChunk applyChunk(MCAChunk chunk, MutableLong count) { - for (int layer = 0; layer < chunk.ids.length; layer++) { - byte[] ids = chunk.ids[layer]; - if (ids != null) { - for (byte i : ids) { - if (allowedId[i & 0xFF]) { - count.increment(); + CountIdFilter counter = new CountIdFilter(); + searchBlocks.forEach(counter::addBlock); + filter = counter; + } + MCAFilterCounter result = runWithWorld(player, folder, filter); + if (result != null) player.print(BBC.getPrefix() + BBC.SELECTION_COUNT.format(result.getTotal())); + } + + @Command( + aliases = {"clear", "unset"}, + desc = "Clear the chunks in a selection (delete without defrag)" + ) + @CommandPermissions("worldedit.anvil.replaceall") + public void unset(Player player, EditSession editSession, @Selection Region selection) throws WorldEditException { + Vector bot = selection.getMinimumPoint(); + Vector top = selection.getMaximumPoint(); + RegionWrapper region = new RegionWrapper(bot, top); + + MCAFilterCounter filter = new MCAFilterCounter() { + @Override + public MCAFile applyFile(MCAFile file) { + int X = file.getX(); + int Z = file.getZ(); + int bcx = X << 5; + int bcz = Z << 5; + int bx = X << 9; + int bz = Z << 9; + if (region.isIn(bx, bz) && region.isIn(bx + 511, bz + 511)) { + file.setDeleted(true); + get().add(512 * 512 * 256); + } else if (region.isInMCA(X, Z)){ + file.init(); + final byte[] empty = new byte[4]; + RandomAccessFile raf = file.getRandomAccessFile(); + file.forEachChunk(new RunnableVal4() { + @Override + public void run(Integer cx, Integer cz, Integer offset, Integer size) { + if (region.isInChunk(bcx + cx, bcz + cz)) { + int index = ((cx & 31) << 2) + ((cz & 31) << 7); + try { + raf.seek(index); + raf.write(empty); + get().add(16 * 16 * 256); + } catch (IOException e) { + e.printStackTrace(); } } } - } - return null; + }); + file.clear(); } - }; + return null; + } + }; + MCAFilterCounter result = runWithSelection(player, editSession, selection, filter); + if (result != null) player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); + } + + @Command( + aliases = {"count"}, + usage = "", + desc = "Count blocks in a selection", + flags = "d", + min = 1, + max = 2 + ) + @CommandPermissions("worldedit.anvil.count") + public void count(Player player, EditSession editSession, @Selection Region selection, String arg, @Switch('d') boolean useData) throws WorldEditException { + Set searchBlocks = worldEdit.getBlocks(player, arg, true); + MCAFilterCounter filter; + if (useData || arg.contains(":")) { // Optimize for both cases + CountFilter counter = new CountFilter(); + searchBlocks.forEach(counter::addBlock); + filter = counter; + } else { + CountIdFilter counter = new CountIdFilter(); + searchBlocks.forEach(counter::addBlock); + filter = counter; } - queue.filterWorld(filter); - player.print(BBC.getPrefix() + BBC.SELECTION_COUNT.format(filter.getTotal())); + MCAFilterCounter result = runWithSelection(player, editSession, selection, filter); + if (result != null) player.print(BBC.getPrefix() + BBC.SELECTION_COUNT.format(result.getTotal())); } @Command( @@ -426,31 +367,8 @@ public class AnvilCommands { } } - private > T runWithSelection(Player player, EditSession editSession, Region selection, T filter) { - if (!(selection instanceof CuboidRegion)) { - BBC.NO_REGION.send(player); - return null; - } - CuboidRegion cuboid = (CuboidRegion) selection; - RegionWrapper wrappedRegion = new RegionWrapper(cuboid.getMinimumPoint(), cuboid.getMaximumPoint()); - String worldName = Fawe.imp().getWorldName(editSession.getWorld()); - FaweQueue tmp = SetQueue.IMP.getNewQueue(worldName, true, false); - File folder = tmp.getSaveFolder(); - MCAQueue queue = new MCAQueue(worldName, folder, tmp.hasSky()); - player.print(BBC.getPrefix() + "Safely unloading regions..."); - tmp.setMCA(new Runnable() { - @Override - public void run() { - player.print(BBC.getPrefix() + "Performing operation..."); - queue.filterRegion(filter, wrappedRegion); - player.print(BBC.getPrefix() + "Safely loading regions..."); - } - }, wrappedRegion, true); - return filter; - } - @Command( - aliases = {"replace"}, + aliases = {"replace", "r"}, usage = "[from-block] ", desc = "Replace all blocks in the selection with another" ) @@ -460,23 +378,45 @@ public class AnvilCommands { if (from == null) { matchFrom = FaweBlockMatcher.NOT_AIR; } else { - if (from.contains(":")) { - useData = true; //override d flag, if they specified data they want it - } - matchFrom = FaweBlockMatcher.fromBlocks(worldEdit.getBlocks(player, from, true), useData); + matchFrom = FaweBlockMatcher.fromBlocks(worldEdit.getBlocks(player, from, true), useData || from.contains(":")); } final FaweBlockMatcher matchTo = FaweBlockMatcher.setBlocks(worldEdit.getBlocks(player, to, true)); - MCAFilterCounter filter = runWithSelection(player, editSession, selection, new MCAFilterCounter() { - @Override - public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong count) { - if (matchFrom.apply(block)) { - matchTo.apply(block); - count.increment(); - } + ReplaceSimpleFilter filter = new ReplaceSimpleFilter(matchFrom, matchTo); + MCAFilterCounter result = runWithSelection(player, editSession, selection, filter); + if (result != null) { + player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); + } + } + + @Command( + aliases = {"replacepattern", "preplace", "rp"}, + usage = "[from-mask] ", + desc = "Replace all blocks in the selection with a pattern" + ) + @CommandPermissions("worldedit.anvil.replace") + // Player player, String folder, @Optional String from, final Pattern to, @Switch('d') boolean useData, @Switch('m') boolean useMap + public void replacePattern(Player player, EditSession editSession, @Selection Region selection, @Optional String from, final Pattern to, @Switch('d') boolean useData, @Switch('m') boolean useMap) throws WorldEditException { + MCAFilterCounter filter; + if (useMap) { + if (to instanceof RandomPattern) { + List split = StringMan.split(from, ','); + filter = new MappedReplacePatternFilter(from, (RandomPattern) to, useData); + } else { + player.print(BBC.getPrefix() + "Must be a pattern list!"); + return; } - }); - if (filter != null) { - player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(filter.getTotal())); + } else { + final FaweBlockMatcher matchFrom; + if (from == null) { + matchFrom = FaweBlockMatcher.NOT_AIR; + } else { + matchFrom = FaweBlockMatcher.fromBlocks(worldEdit.getBlocks(player, from, true), useData || from.contains(":")); + } + filter = new ReplacePatternFilter(matchFrom, to); + } + MCAFilterCounter result = runWithSelection(player, editSession, selection, filter); + if (result != null) { + player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); } } @@ -492,39 +432,10 @@ public class AnvilCommands { Vector max = selection.getMaximumPoint(); int minY = min.getBlockY(); int maxY = max.getBlockY(); - final int startLayer = minY >> 4; - final int endLayer = maxY >> 4; - MCAFilterCounter filter = runWithSelection(player, editSession, selection, new MCAFilterCounter() { - @Override - public MCAChunk applyChunk(MCAChunk chunk, MutableLong cache) { - for (int layer = startLayer; layer <= endLayer; layer++) { - byte[] ids = chunk.ids[layer]; - if (ids == null) { - return null; - } - int startY = Math.max(minY, layer << 4) & 15; - int endY = Math.min(maxY, 15 + (layer << 4)) & 15; - for (int y = startY; y <= endY; y++) { - int indexStart = y << 8; - int indexEnd = indexStart + 255; - for (int index = indexStart; index <= indexEnd; index++) { - if (ids[index] != id) { - return null; - } - } - } - for (int y = startY; y <= endY; y++) { - int indexStart = y << 8; - int indexEnd = indexStart + 255; - ArrayUtil.fill(ids, indexStart, indexEnd + 1, (byte) 0); - } - chunk.setModified(); - } - return null; - } - }); - if (filter != null) { - player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(filter.getTotal())); + RemoveLayerFilter filter = new RemoveLayerFilter(minY, maxY, id); + MCAFilterCounter result = runWithSelection(player, editSession, selection, filter); + if (result != null) { + player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(result.getTotal())); } } @@ -601,6 +512,5 @@ public class AnvilCommands { }, copyRegion, false); } }, pasteRegion, true); - player.print("Done!"); } } \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/config/BBC.java b/core/src/main/java/com/boydti/fawe/config/BBC.java index 5c6abd4f..791f2bc5 100644 --- a/core/src/main/java/com/boydti/fawe/config/BBC.java +++ b/core/src/main/java/com/boydti/fawe/config/BBC.java @@ -106,6 +106,8 @@ public enum BBC { SELECTION_SHIFT("Region shifted", "WorldEdit.Selection"), SELECTION_CLEARED("Selection cleared", "WorldEdit.Selection"), + WORLD_IS_LOADED("The world shouldn't be in use when executing. Unload the world, or use use -f to override (save first)", "WorldEdit.Anvil"), + BRUSH_RESET("Reset your brush.", "WorldEdit.Brush"), BRUSH_NONE("You aren't holding a brush!", "WorldEdit.Brush"), BRUSH_SCROLL_ACTION_SET("Set scroll action to %s0", "WorldEdit.Brush"), diff --git a/core/src/main/java/com/boydti/fawe/config/Settings.java b/core/src/main/java/com/boydti/fawe/config/Settings.java index 1d4b3c9a..2f5397e6 100644 --- a/core/src/main/java/com/boydti/fawe/config/Settings.java +++ b/core/src/main/java/com/boydti/fawe/config/Settings.java @@ -72,6 +72,7 @@ public class Settings extends Config { "Put any minecraft or mod jars for FAWE to be aware of block textures", }) public String TEXTURES = "textures"; + public String HEIGHTMAP = "heightmap"; public String HISTORY = "history"; public String CLIPBOARD = "clipboard"; @Comment("Each player has their own sub directory for schematics") @@ -281,15 +282,22 @@ public class Settings extends Config { } @Comment({ - "Experimental options, use at your own risk", - " - Apparently that wasn't enough, need an all caps warning?", - " - DO NOT USE IF YOU ARE CLUELESS!" + "Experimental options, use at your own risk" }) public static class EXPERIMENTAL { @Comment({ - "Directly modify the region files.", + "[UNSAFE] Directly modify the region files. (OBSOLETE - USE ANVIL COMMANDS)", + " - IMPROPER USE CAN CAUSE WORLD CORRUPTION!", }) public boolean ANVIL_QUEUE_MODE = false; + @Comment({ + "[SAFE] Dynamically increase the number of chunks rendered", + " - Requires Paper: ci.destroystokyo.com/job/PaperSpigot/", + " - Set your server view distance to 1 (spigot.yml, server.properties)", + " - Based on tps and player movement", + " - Please provide feedback", + }) + public boolean DYNAMIC_CHUNK_RENDERING = false; } public static class WEB { diff --git a/core/src/main/java/com/boydti/fawe/jnbt/NBTStreamer.java b/core/src/main/java/com/boydti/fawe/jnbt/NBTStreamer.java index 379947f9..bc106a11 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/NBTStreamer.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/NBTStreamer.java @@ -16,6 +16,10 @@ public class NBTStreamer { readers = new HashMap<>(); } + /** + * Reads the entire stream and runs the applicable readers + * @throws IOException + */ public void readFully() throws IOException { is.readNamedTagLazy(new RunnableVal2() { @Override @@ -26,6 +30,12 @@ public class NBTStreamer { is.close(); } + /** + * Reads the stream until all readers have been used
+ * - Use readFully if you expect a reader to appear more than once + * - Can exit early without having reading the entire file + * @throws IOException + */ public void readQuick() throws IOException { try { is.readNamedTagLazy(new RunnableVal2() { diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java index 209f8123..9239518d 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java @@ -83,6 +83,8 @@ public class HeightMapMCAGenerator extends MCAWriter implements Extent { try { if (randomVariation) { return new RandomTextureUtil(textureUtil); + } else if (textureUtil instanceof CachedTextureUtil) { + return textureUtil; } else { return new CachedTextureUtil(textureUtil); } @@ -807,8 +809,6 @@ public class HeightMapMCAGenerator extends MCAWriter implements Extent { @Override public MCAChunk write(MCAChunk chunk, int csx, int cex, int csz, int cez) { try { - int cx = chunk.getX(); - int cz = chunk.getZ(); int[] indexes = indexStore.get(); for (int i = 0; i < chunk.ids.length; i++) { byte[] idsArray = chunk.ids[i]; @@ -848,6 +848,27 @@ public class HeightMapMCAGenerator extends MCAWriter implements Extent { chunk.blockLight[layer] = new byte[2048]; } } + if (waterHeight != 0) { + maxY = Math.max(maxY, waterHeight); + int maxWaterLayer = ((waterHeight + 15) >> 4); + for (int layer = 0; layer < maxWaterLayer; layer++) { + boolean fillAll = (layer << 4) + 15 <= waterHeight; + byte[] ids = chunk.ids[layer]; + if (ids == null) { + chunk.ids[layer] = ids = new byte[4096]; + chunk.data[layer] = new byte[2048]; + chunk.skyLight[layer] = new byte[2048]; + chunk.blockLight[layer] = new byte[2048]; + Arrays.fill(chunk.skyLight[layer], (byte) 255); + } + if (fillAll) { + Arrays.fill(ids, waterId); + } else { + int maxIndex = maxWaterLayer << 8; + Arrays.fill(ids, 0, maxIndex, waterId); + } + } + } if (modifiedMain) { // If the main block is modified, we can't short circuit this for (int layer = 0; layer < fillLayers; layer++) { byte[] layerIds = chunk.ids[layer]; @@ -876,27 +897,6 @@ public class HeightMapMCAGenerator extends MCAWriter implements Extent { Arrays.fill(chunk.ids[layer], (byte) 1); } } - if (waterHeight != 0) { - maxY = Math.max(maxY, waterHeight); - int maxWaterLayer = ((waterHeight + 15) >> 4); - for (int layer = 0; layer < maxWaterLayer; layer++) { - boolean fillAll = (layer << 4) + 15 <= waterHeight; - byte[] ids = chunk.ids[layer]; - if (ids == null) { - chunk.ids[layer] = ids = new byte[4096]; - chunk.data[layer] = new byte[2048]; - chunk.skyLight[layer] = new byte[2048]; - chunk.blockLight[layer] = new byte[2048]; - Arrays.fill(chunk.skyLight[layer], (byte) 255); - } - if (fillAll) { - Arrays.fill(ids, waterId); - } else { - int maxIndex = maxWaterLayer << 8; - Arrays.fill(ids, 0, maxIndex, waterId); - } - } - } for (int layer = fillLayers; layer <= maxLayer; layer++) { Arrays.fill(chunk.skyLight[layer], (byte) 255); byte[] layerIds = chunk.ids[layer]; @@ -973,7 +973,7 @@ public class HeightMapMCAGenerator extends MCAWriter implements Extent { } } } - int chunkPair = MathMan.pair((short) cx, (short) cz); + int chunkPair = MathMan.pair((short) chunk.getX(), (short) chunk.getZ()); char[][][] localBlocks = blocks.get(chunkPair); if (localBlocks != null) { for (int layer = 0; layer < 16; layer++) { diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFile.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFile.java index c1a9bf66..8f4ef295 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFile.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAFile.java @@ -137,6 +137,10 @@ public class MCAFile { return Z; } + public RandomAccessFile getRandomAccessFile() { + return raf; + } + public File getFile() { return file; } @@ -181,6 +185,11 @@ public class MCAFile { return chunk; } + /** + * CX, CZ, OFFSET, SIZE + * @param onEach + * @throws IOException + */ public void forEachSortedChunk(RunnableVal4 onEach) throws IOException { char[] offsets = new char[(int) (raf.length() / 4096) - 2]; Arrays.fill(offsets, Character.MAX_VALUE); diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAWriter.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAWriter.java index 8570375d..10289ec7 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAWriter.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAWriter.java @@ -2,7 +2,6 @@ package com.boydti.fawe.jnbt.anvil; import com.boydti.fawe.object.io.BufferedRandomAccessFile; import com.boydti.fawe.util.MainUtil; -import com.boydti.fawe.util.MathMan; import java.io.File; import java.io.IOException; import java.util.concurrent.ForkJoinPool; @@ -14,6 +13,8 @@ public abstract class MCAWriter { private final int length; private final int width; private final int area; + private int OX, OZ; + public MCAWriter(int width, int length, File regionFolder) { if (!regionFolder.exists()) { @@ -37,6 +38,26 @@ public abstract class MCAWriter { return length; } + /** + * Set the MCA file offset (each mca file is 512 blocks) + * - A negative value will shift the map negative + * - This only applies to generation, not block get/set + * @param mcaOX + * @param mcaOZ + */ + public void setMCAOffset(int mcaOX, int mcaOZ) { + OX = mcaOX << 9; + OZ = mcaOZ << 9; + } + + public int getOffsetX() { + return OX; + } + + public int getOffsetZ() { + return OZ; + } + public final int getArea() { return area; } @@ -79,11 +100,16 @@ public abstract class MCAWriter { } }; byte[] fileBuf = new byte[1 << 16]; - for (int mcaZ = 0; mcaZ <= (length >> 9); mcaZ++) { - for (int mcaX = 0; mcaX <= (width >> 9); mcaX++) { + int mcaXMin = 0; + int mcaZMin = 0; + int mcaXMax = mcaXMin + width >> 9; + int mcaZMax = mcaZMin + length >> 9; + + for (int mcaZ = mcaXMin; mcaZ <= mcaZMax; mcaZ++) { + for (int mcaX = mcaXMin; mcaX <= mcaXMax; mcaX++) { final int fmcaX = mcaX; final int fmcaZ = mcaZ; - File file = new File(folder, "r." + mcaX + "." + mcaZ + ".mca"); + File file = new File(folder, "r." + (mcaX + (getOffsetX() >> 9)) + "." + (mcaZ + (getOffsetZ() >> 9)) + ".mca"); if (!file.exists()) { file.createNewFile(); } @@ -96,7 +122,6 @@ public abstract class MCAWriter { int ecx = Math.min(scx + 31, tcx); int scz = bz >> 4; int ecz = Math.min(scz + 31, tcz); - short pair = MathMan.pairByte(mcaX, mcaZ); for (int cz = scz; cz <= ecz; cz++) { final int csz = cz << 4; final int cez = Math.min(csz + 15, length - 1); @@ -114,6 +139,9 @@ public abstract class MCAWriter { chunk.setLoc(null, fcx, fcz); chunk = write(chunk, csx, cex, csz, cez); if (chunk != null) { + // Generation offset + chunk.setLoc(null, fcx + (getOffsetX() >> 4), fcz + (getOffsetZ() >> 4)); + // Compress byte[] bytes = chunk.toBytes(byteStore1.get()); byte[] compressedBytes = MainUtil.compress(bytes, byteStore2.get(), deflateStore.get()); int blocks = (compressed.length + 4095) >> 12; diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/CountFilter.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/CountFilter.java new file mode 100644 index 00000000..1a422055 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/CountFilter.java @@ -0,0 +1,50 @@ +package com.boydti.fawe.jnbt.anvil.filters; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.jnbt.anvil.MCAChunk; +import com.boydti.fawe.jnbt.anvil.MCAFilterCounter; +import com.boydti.fawe.object.number.MutableLong; +import com.sk89q.worldedit.blocks.BaseBlock; + +public class CountFilter extends MCAFilterCounter { + private final boolean[] allowedId = new boolean[FaweCache.getId(Character.MAX_VALUE)]; + private final boolean[] allowed = new boolean[Character.MAX_VALUE]; + + public CountFilter() {} + + public CountFilter addBlock(BaseBlock block) { + addBlock(block.getId(), block.getData()); + return this; + } + + public CountFilter addBlock(int id, int data) { + allowedId[id] = true; + allowed[FaweCache.getCombined(id, data)] = true; + return this; + } + + @Override + public MCAChunk applyChunk(MCAChunk chunk, MutableLong count) { + for (int layer = 0; layer < chunk.ids.length; layer++) { + byte[] ids = chunk.ids[layer]; + if (ids == null) { + continue; + } + byte[] datas = chunk.data[layer]; + for (int i = 0; i < ids.length; i++) { + int id = ids[i] & 0xFF; + if (!allowedId[id]) { + continue; + } + int combined = (id) << 4; + if (FaweCache.hasData(id)) { + combined += chunk.getNibble(i, datas); + } + if (allowed[combined]) { + count.increment(); + } + } + } + return null; + } +} diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/CountIdFilter.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/CountIdFilter.java new file mode 100644 index 00000000..71264501 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/CountIdFilter.java @@ -0,0 +1,38 @@ +package com.boydti.fawe.jnbt.anvil.filters; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.jnbt.anvil.MCAChunk; +import com.boydti.fawe.jnbt.anvil.MCAFilterCounter; +import com.boydti.fawe.object.number.MutableLong; +import com.sk89q.worldedit.blocks.BaseBlock; + +public class CountIdFilter extends MCAFilterCounter { + private final boolean[] allowedId = new boolean[FaweCache.getId(Character.MAX_VALUE)]; + + public CountIdFilter() {} + + public CountIdFilter addBlock(int id) { + allowedId[id] = true; + return this; + } + + public CountIdFilter addBlock(BaseBlock block) { + allowedId[block.getId()] = true; + return this; + } + + @Override + public MCAChunk applyChunk(MCAChunk chunk, MutableLong count) { + for (int layer = 0; layer < chunk.ids.length; layer++) { + byte[] ids = chunk.ids[layer]; + if (ids != null) { + for (byte i : ids) { + if (allowedId[i & 0xFF]) { + count.increment(); + } + } + } + } + return null; + } +} diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/DeleteUninhabitedFilter.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/DeleteUninhabitedFilter.java new file mode 100644 index 00000000..5dfe9a54 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/DeleteUninhabitedFilter.java @@ -0,0 +1,92 @@ +package com.boydti.fawe.jnbt.anvil.filters; + +import com.boydti.fawe.jnbt.NBTStreamer; +import com.boydti.fawe.jnbt.anvil.MCAChunk; +import com.boydti.fawe.jnbt.anvil.MCAFile; +import com.boydti.fawe.jnbt.anvil.MCAFilterCounter; +import com.boydti.fawe.object.RunnableVal; +import com.boydti.fawe.object.RunnableVal2; +import com.boydti.fawe.object.RunnableVal4; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; + +/** + * Deletes unvisited MCA files and Chunks
+ * - This a global filter and cannot be used a selection
+ */ +public class DeleteUninhabitedFilter extends MCAFilterCounter { + private final long inhabitedTicks; + private final long fileAgeMillis; + + public DeleteUninhabitedFilter(long fileAgeMillis, long inhabitedTicks) { + this.fileAgeMillis = fileAgeMillis; + this.inhabitedTicks = inhabitedTicks; + } + @Override + public MCAFile applyFile(MCAFile mca) { + File file = mca.getFile(); + try { + BasicFileAttributes attr = Files.readAttributes(file.toPath(), BasicFileAttributes.class); + long creation = attr.creationTime().toMillis(); + long modified = attr.lastModifiedTime().toMillis(); + if (modified - creation < fileAgeMillis && modified > creation) { + mca.setDeleted(true); + get().add(512 * 512 * 256); + return null; + } + } catch (IOException | UnsupportedOperationException ignore) {} + try { + ForkJoinPool pool = new ForkJoinPool(); + mca.init(); + mca.forEachSortedChunk(new RunnableVal4() { + @Override + public void run(Integer x, Integer z, Integer offset, Integer size) { + try { + byte[] bytes = mca.getChunkCompressedBytes(offset); + if (bytes == null) return; + Runnable task = new Runnable() { + @Override + public void run() { + try { + mca.streamChunk(offset, new RunnableVal() { + @Override + public void run(NBTStreamer value) { + value.addReader(".Level.InhabitedTime", new RunnableVal2() { + @Override + public void run(Integer index, Long value) { + if (value <= inhabitedTicks) { + MCAChunk chunk = new MCAChunk(null, x, z); + chunk.setDeleted(true); + synchronized (mca) { + mca.setChunk(chunk); + } + get().add(16 * 16 * 256); + } + } + }); + } + }); + } catch (IOException e) { + e.printStackTrace(); + } + } + }; + pool.submit(task); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + mca.close(pool); + pool.shutdown(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/MappedReplacePatternFilter.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/MappedReplacePatternFilter.java new file mode 100644 index 00000000..e64c467b --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/MappedReplacePatternFilter.java @@ -0,0 +1,71 @@ +package com.boydti.fawe.jnbt.anvil.filters; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.jnbt.anvil.MCAFilterCounter; +import com.boydti.fawe.object.number.MutableLong; +import com.boydti.fawe.util.StringMan; +import com.sk89q.worldedit.MutableBlockVector; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.function.pattern.RandomPattern; +import java.util.ArrayList; +import java.util.List; + +public class MappedReplacePatternFilter extends MCAFilterCounter { + private Pattern[] map = new Pattern[Character.MAX_VALUE + 1]; + + public MappedReplacePatternFilter() {} + + public MappedReplacePatternFilter(String from, RandomPattern to, boolean useData) throws InputParseException { + List split = StringMan.split(from, ','); + Pattern[] patterns = ((RandomPattern) to).getPatterns().toArray(new Pattern[0]); + if (patterns.length == split.size()) { + for (int i = 0; i < split.size(); i++) { + Pattern pattern = patterns[i]; + String arg = split.get(i); + ArrayList blocks = new ArrayList(); + for (String arg2 : arg.split(",")) { + BaseBlock block = FaweCache.getBlock(arg, true, !useData); + blocks.add(block); + } + for (BaseBlock block : blocks) { + if (block.getData() != -1) { + int combined = FaweCache.getCombined(block); + map[combined] = pattern; + } else { + for (int data = 0; data < 16; data++) { + int combined = FaweCache.getCombined(block.getId(), data); + map[combined] = pattern; + } + } + } + } + } else { + throw new InputParseException("Mask:Pattern must be a 1:1 match"); + } + } + + public void addReplace(BaseBlock block, Pattern pattern) { + map[block.getCombined()] = pattern; + } + + private final MutableBlockVector mutable = new MutableBlockVector(0, 0, 0); + + @Override + public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong ignore) { + int id = block.getId(); + int data = FaweCache.hasData(id) ? block.getData() : 0; + int combined = FaweCache.getCombined(id, data); + Pattern p = map[combined]; + if (p != null) { + BaseBlock newBlock = p.apply(x, y, z); + int currentId = block.getId(); + if (FaweCache.hasNBT(currentId)) { + block.setNbtData(null); + } + block.setId(newBlock.getId()); + block.setData(newBlock.getData()); + } + } +} diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/RemoveLayerFilter.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/RemoveLayerFilter.java new file mode 100644 index 00000000..3e270285 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/RemoveLayerFilter.java @@ -0,0 +1,50 @@ +package com.boydti.fawe.jnbt.anvil.filters; + +import com.boydti.fawe.jnbt.anvil.MCAChunk; +import com.boydti.fawe.jnbt.anvil.MCAFilterCounter; +import com.boydti.fawe.object.number.MutableLong; +import com.boydti.fawe.util.ArrayUtil; + +public class RemoveLayerFilter extends MCAFilterCounter { + private final int startLayer; + private final int endLayer; + private final int minY, maxY; + private final int id; + + public RemoveLayerFilter(int minY, int maxY, int id) { + this.minY = minY; + this.maxY = maxY; + this.startLayer = minY >> 4; + this.endLayer = maxY >> 4; + this.id = id; + } + + + @Override + public MCAChunk applyChunk(MCAChunk chunk, MutableLong cache) { + for (int layer = startLayer; layer <= endLayer; layer++) { + byte[] ids = chunk.ids[layer]; + if (ids == null) { + return null; + } + int startY = Math.max(minY, layer << 4) & 15; + int endY = Math.min(maxY, 15 + (layer << 4)) & 15; + for (int y = startY; y <= endY; y++) { + int indexStart = y << 8; + int indexEnd = indexStart + 255; + for (int index = indexStart; index <= indexEnd; index++) { + if (ids[index] != id) { + return null; + } + } + } + for (int y = startY; y <= endY; y++) { + int indexStart = y << 8; + int indexEnd = indexStart + 255; + ArrayUtil.fill(ids, indexStart, indexEnd + 1, (byte) 0); + } + chunk.setModified(); + } + return null; + } +} diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/ReplacePatternFilter.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/ReplacePatternFilter.java new file mode 100644 index 00000000..c8cb7195 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/ReplacePatternFilter.java @@ -0,0 +1,31 @@ +package com.boydti.fawe.jnbt.anvil.filters; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.jnbt.anvil.MCAFilterCounter; +import com.boydti.fawe.object.mask.FaweBlockMatcher; +import com.boydti.fawe.object.number.MutableLong; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.function.pattern.Pattern; + +public class ReplacePatternFilter extends MCAFilterCounter { + private final FaweBlockMatcher matchFrom; + private final Pattern to; + + public ReplacePatternFilter(FaweBlockMatcher from, Pattern to) { + this.matchFrom = from; + this.to = to; + } + + @Override + public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong ignore) { + if (matchFrom.apply(block)) { + BaseBlock newBlock = to.apply(x, y, z); + int currentId = block.getId(); + if (FaweCache.hasNBT(currentId)) { + block.setNbtData(null); + } + block.setId(newBlock.getId()); + block.setData(newBlock.getData()); + } + } +} diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/ReplaceSimpleFilter.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/ReplaceSimpleFilter.java new file mode 100644 index 00000000..2e2e270a --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/filters/ReplaceSimpleFilter.java @@ -0,0 +1,23 @@ +package com.boydti.fawe.jnbt.anvil.filters; + +import com.boydti.fawe.jnbt.anvil.MCAFilterCounter; +import com.boydti.fawe.object.mask.FaweBlockMatcher; +import com.boydti.fawe.object.number.MutableLong; +import com.sk89q.worldedit.blocks.BaseBlock; + +public class ReplaceSimpleFilter extends MCAFilterCounter { + private final FaweBlockMatcher to; + private final FaweBlockMatcher from; + + public ReplaceSimpleFilter(FaweBlockMatcher from, FaweBlockMatcher to) { + this.from = from; + this.to = to; + } + @Override + public void applyBlock(int x, int y, int z, BaseBlock block, MutableLong count) { + if (from.apply(block)) { + to.apply(block); + count.increment(); + } + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/collection/LocalBlockVector2DSet.java b/core/src/main/java/com/boydti/fawe/object/collection/LocalBlockVector2DSet.java index f6677717..d08858e3 100644 --- a/core/src/main/java/com/boydti/fawe/object/collection/LocalBlockVector2DSet.java +++ b/core/src/main/java/com/boydti/fawe/object/collection/LocalBlockVector2DSet.java @@ -14,7 +14,6 @@ import java.util.Set; * - This will use 8 bytes for every 64 Vector2Ds (about 800x less than a HashSet) */ public class LocalBlockVector2DSet implements Set { - private boolean chunksValid = true; private final SparseBitSet set; private final MutableBlockVector2D mutable = new MutableBlockVector2D(); @@ -115,7 +114,6 @@ public class LocalBlockVector2DSet implements Set { int previous = -1; @Override public void remove() { - chunksValid = false; set.clear(previous); } @Override @@ -193,7 +191,6 @@ public class LocalBlockVector2DSet implements Set { boolean value = set.get(index); if (value) { set.clear(index); - chunksValid = false; } return value; } @@ -239,7 +236,6 @@ public class LocalBlockVector2DSet implements Set { if (!c.contains(mutable)) { result = true; set.clear(index); - chunksValid = false; } } return result; @@ -273,6 +269,5 @@ public class LocalBlockVector2DSet implements Set { @Override public void clear() { set.clear(); - chunksValid = true; } } diff --git a/core/src/main/java/com/boydti/fawe/object/collection/PrimitiveList.java b/core/src/main/java/com/boydti/fawe/object/collection/PrimitiveList.java index 00f6309e..a1ac9d61 100644 --- a/core/src/main/java/com/boydti/fawe/object/collection/PrimitiveList.java +++ b/core/src/main/java/com/boydti/fawe/object/collection/PrimitiveList.java @@ -178,6 +178,23 @@ public class PrimitiveList extends AbstractList { } } + public void set(int index, long value) { + switch (type) { + default: + setFast(index, value); + return; + case Integer: + ((int[]) arr)[index] = (int) value; + return; + case Long: + ((long[]) arr)[index] = value; + return; + case Double: + ((double[]) arr)[index] = (double) value; + return; + } + } + public void set(int index, double value) { switch (type) { default: @@ -271,6 +288,12 @@ public class PrimitiveList extends AbstractList { return true; } + public boolean add(long element) { + ensureAddCapacity(); + set(length++, element); + return true; + } + public boolean add(double element) { ensureAddCapacity(); set(length++, element); diff --git a/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java b/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java index e6b25e64..9bd224a4 100644 --- a/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java +++ b/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java @@ -8,6 +8,7 @@ import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.util.CleanTextureUtil; import com.boydti.fawe.util.FilteredTextureUtil; +import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.StringMan; import com.boydti.fawe.util.TaskManager; import com.intellectualcrafters.plot.PS; @@ -25,7 +26,6 @@ import com.intellectualcrafters.plot.object.RunnableVal3; import com.intellectualcrafters.plot.object.worlds.PlotAreaManager; import com.intellectualcrafters.plot.object.worlds.SinglePlotArea; import com.intellectualcrafters.plot.object.worlds.SinglePlotAreaManager; -import com.intellectualcrafters.plot.util.MainUtil; import com.plotsquared.general.commands.Command; import com.plotsquared.general.commands.CommandDeclaration; import com.sk89q.worldedit.Vector; @@ -85,7 +85,8 @@ public class CreateFromImage extends Command { if (generator == null) { final Vector2D dimensions; final BufferedImage image; - if (argList.get(0).toLowerCase().startsWith("http")) { + String arg0 = argList.get(0).toLowerCase(); + if (arg0.startsWith("http") || arg0.startsWith("file://")) { try { player.sendMessage(BBC.getPrefix() + "Loading image... (1)"); image = getImage(argList.get(0), fp); @@ -113,7 +114,7 @@ public class CreateFromImage extends Command { int currentPlots = Settings.Limit.GLOBAL ? player.getPlotCount() : player.getPlotCount(area.worldname); int diff = player.getAllowedPlots() - currentPlots; if (diff < 1) { - MainUtil.sendMessage(player, C.CANT_CLAIM_MORE_PLOTS_NUM, -diff + ""); + C.CANT_CLAIM_MORE_PLOTS_NUM.send(player, -diff); return; } if (area.getMeta("lastPlot") == null) { @@ -195,7 +196,8 @@ public class CreateFromImage extends Command { } int argOffset = 0; BufferedImage img = null; - if (argList.get(1).startsWith("http")) { + String arg1 = argList.get(1); + if (arg1.startsWith("http") || arg1.startsWith("file://")) { img = getImage(argList.get(1), fp); argOffset++; } @@ -601,7 +603,12 @@ public class CreateFromImage extends Command { if (arg.startsWith("http")) { URL url = new URL(arg); fp.sendMessage(BBC.getPrefix() + "Downloading image... (3)"); - return com.boydti.fawe.util.MainUtil.toRGB(ImageIO.read(url)); + return MainUtil.toRGB(ImageIO.read(url)); + } + if (arg.startsWith("file://")) { + arg = arg.substring(7); + File file = MainUtil.getFile(Fawe.imp().getDirectory(), com.boydti.fawe.config.Settings.IMP.PATHS.HEIGHTMAP + File.separator + arg); + return MainUtil.toRGB(ImageIO.read(file)); } return null; } diff --git a/core/src/main/java/com/boydti/fawe/util/task/TaskBuilder.java b/core/src/main/java/com/boydti/fawe/util/task/TaskBuilder.java index a94bd343..37b165dd 100644 --- a/core/src/main/java/com/boydti/fawe/util/task/TaskBuilder.java +++ b/core/src/main/java/com/boydti/fawe/util/task/TaskBuilder.java @@ -464,6 +464,7 @@ public class TaskBuilder extends Metadatable { private long start; private Object asyncWaitLock = new Object(); private Object syncWaitLock = new Object(); + private boolean finished; public SplitTask() { @@ -483,6 +484,7 @@ public class TaskBuilder extends Metadatable { public void run() { try { synchronized (asyncWaitLock) { + asyncWaitLock.notifyAll(); asyncWaitLock.wait(Long.MAX_VALUE); } } catch (InterruptedException e) { @@ -495,24 +497,31 @@ public class TaskBuilder extends Metadatable { } } }); - thread.start(); + try { + synchronized (asyncWaitLock) { + thread.start(); + asyncWaitLock.wait(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } while (thread.isAlive()) { TaskManager.IMP.syncWhenFree(new RunnableVal() { @Override public void run(Object ignore) { queue.startSet(true); start = System.currentTimeMillis(); - synchronized (asyncWaitLock) { - asyncWaitLock.notifyAll(); - } - synchronized (syncWaitLock) { + try { if (!finished) { - try { - syncWaitLock.wait(Long.MAX_VALUE); - } catch (InterruptedException e) { - e.printStackTrace(); + synchronized (asyncWaitLock) { + asyncWaitLock.notifyAll(); + } + synchronized (syncWaitLock) { + syncWaitLock.wait(); } } + } catch (InterruptedException e) { + e.printStackTrace(); } queue.endSet(true); } @@ -529,7 +538,7 @@ public class TaskBuilder extends Metadatable { syncWaitLock.notifyAll(); } synchronized (asyncWaitLock) { - asyncWaitLock.wait(Long.MAX_VALUE); + asyncWaitLock.wait(); } } catch (InterruptedException e) { e.printStackTrace(); diff --git a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java index 8b6ac558..505c9d97 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -680,7 +680,7 @@ public class BrushCommands extends MethodCommands { private InputStream getHeightmapStream(String filename) { String filenamePng = (filename.endsWith(".png") ? filename : filename + ".png"); - File file = new File(Fawe.imp().getDirectory(), "heightmap" + File.separator + filenamePng); + File file = new File(Fawe.imp().getDirectory(), Settings.IMP.PATHS.HEIGHTMAP + File.separator + filenamePng); if (!file.exists()) { if (!filename.equals("#clipboard") && filename.length() >= 7) { try { diff --git a/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java b/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java index 4f579398..9c7076dc 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java @@ -674,7 +674,8 @@ public class SelectionCommands { if (useData) { for (Countable c : distributionData) { - String name = BlockType.fromID(c.getID().getId()).getName(); + BlockType block = BlockType.fromID(c.getID().getId()); + String name = block != null ? block.getName() : null; String str = String.format("%-7s (%.3f%%) %s #%d:%d", String.valueOf(c.getAmount()), c.getAmount() / (double) size * 100, diff --git a/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java b/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java index e5c04f56..650a3ea1 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/platform/CommandManager.java @@ -379,9 +379,6 @@ public final class CommandManager { try { Object result = dispatcher.call(Joiner.on(" ").join(split), locals, new String[0]); } catch (Throwable t) { - while (t.getCause() != null) { - t = t.getCause(); - } // Use the exception converter to convert the exception if any of its causes // can be converted, otherwise throw the original exception Throwable next = t; @@ -407,26 +404,22 @@ public final class CommandManager { BBC.COMMAND_SYNTAX.send(finalActor, e.getSimpleUsageString("/")); } } catch (WrappedCommandException e) { - FaweException faweException = FaweException.get(e); + Exception faweException = FaweException.get(e); + String message = e.getMessage(); if (faweException != null) { BBC.WORLDEDIT_CANCEL_REASON.send(finalActor, faweException.getMessage()); } else { - Throwable t = e.getCause(); - while (t.getCause() != null) { - t = t.getCause(); - } finalActor.printError("There was an error handling a FAWE command: [See console]"); - finalActor.printRaw(t.getClass().getName() + ": " + t.getMessage()); - log.log(Level.SEVERE, "An unexpected error occurred while handling a FAWE command", t); + finalActor.printRaw(e.getClass().getName() + ": " + e.getMessage()); + log.log(Level.SEVERE, "An unexpected error occurred while handling a FAWE command", e); } - } catch (Throwable e) { - e.printStackTrace(); + } catch (CommandException e) { String message = e.getMessage(); if (message != null) { - finalActor.printError(e.getMessage()); + actor.printError(e.getMessage()); } else { - finalActor.printError("An unknown error has occurred! Please see console."); - log.log(Level.SEVERE, "An unknown error occurred", e); + actor.printError("An unknown FAWE error has occurred! Please see console."); + log.log(Level.SEVERE, "An unknown FAWE error occurred", e); } } finally { final EditSession editSession = locals.get(EditSession.class);