diff --git a/.gitignore b/.gitignore index 9ae1bb7f..3bcf1345 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ *.log gradle.log /lib +/core/lib +/bukkit/lib /core/build /forge189/build /forge1710/build diff --git a/bukkit/build.gradle b/bukkit/build.gradle index 9c9cd9de..034b6f55 100644 --- a/bukkit/build.gradle +++ b/bukkit/build.gradle @@ -1,3 +1,6 @@ +repositories { + flatDir {dirs 'lib'} +} dependencies { compile project(':core') compile 'org.bukkit.craftbukkitv1_10:craftbukkitv1_10:1.10' @@ -37,8 +40,8 @@ apply plugin: 'com.github.johnrengelman.shadow' shadowJar { dependencies { include(dependency('com.github.luben:zstd-jni:1.1.1')) - include(dependency('org.javassist:javassist:3.22.0-CR1')) - include(dependency('co.aikar:fastutil-lite:1.0')) +// include(dependency('org.javassist:javassist:3.22.0-CR1')) + include(dependency('it.unimi.dsi:fastutil:6.5.1')) include(dependency(':core')) } archiveName = "${parent.name}-${project.name}-${parent.version}.jar" diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitMain.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitMain.java index 20ba3026..5048e878 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitMain.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/BukkitMain.java @@ -5,7 +5,6 @@ import com.boydti.fawe.bukkit.v0.BukkitQueue_0; import com.boydti.fawe.bukkit.v0.BukkitQueue_All; import com.boydti.fawe.bukkit.v1_10.BukkitQueue_1_10; import com.boydti.fawe.bukkit.v1_11.BukkitQueue_1_11; -import com.boydti.fawe.bukkit.v1_11.compression.CompressionOptimizer; import com.boydti.fawe.bukkit.v1_7.BukkitQueue17; import com.boydti.fawe.bukkit.v1_8.BukkitQueue18R3; import com.boydti.fawe.bukkit.v1_9.BukkitQueue_1_9_R1; @@ -69,15 +68,6 @@ public class BukkitMain extends JavaPlugin { break; } catch (IllegalStateException e) {} } - switch (version) { - case v1_11_R1: - try { - CompressionOptimizer optimizer = new CompressionOptimizer(); -// optimizer.optimize(); - } catch (Throwable throwable) { - throwable.printStackTrace(); - } - } } private enum Version { diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java index 7567cb16..4c781603 100644 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java +++ b/bukkit/src/main/java/com/boydti/fawe/bukkit/v0/BukkitQueue_0.java @@ -195,7 +195,8 @@ public abstract class BukkitQueue_0 extends NMSMa @Override public boolean hasSky() { - return getWorld().getEnvironment() == World.Environment.NORMAL; + World world = getWorld(); + return world == null || world.getEnvironment() == World.Environment.NORMAL; } private volatile boolean timingsEnabled; diff --git a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_11/compression/CompressionOptimizer.java b/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_11/compression/CompressionOptimizer.java deleted file mode 100644 index eb7a2828..00000000 --- a/bukkit/src/main/java/com/boydti/fawe/bukkit/v1_11/compression/CompressionOptimizer.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.boydti.fawe.bukkit.v1_11.compression; - -import com.boydti.fawe.util.MainUtil; -import java.io.FileInputStream; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; -import javassist.ClassPool; -import javassist.CtClass; -import javassist.CtMethod; -import javassist.LoaderClassPath; -import net.minecraft.server.v1_11_R1.RegionFile; - -public class CompressionOptimizer { - - private final ClassPool pool; - - public CompressionOptimizer() { - this.pool = ClassPool.getDefault(); - } - - public void loadSafe(String name) throws Throwable { - try { - pool.get(name).toClass(); - } catch (Throwable e) { - while (e.getCause() != null) { - e = e.getCause(); - } - if (e instanceof ClassNotFoundException) { - loadSafe(e.getMessage()); - } - } - } - - public void loadPackage(String... packages) throws IOException { - JarInputStream jarFile = new JarInputStream(new FileInputStream(MainUtil.getJarFile())); - JarEntry jarEntry; - while(true) { - jarEntry = jarFile.getNextJarEntry(); - if(jarEntry == null){ - break; - } - if (jarEntry.getName ().endsWith (".class")) { - for (String p : packages) { - if (jarEntry.getName ().startsWith(p)) { - String name = jarEntry.getName().substring(0, jarEntry.getName().length() - 6).replaceAll("/", "\\."); - try { - loadSafe(name); - } catch (Throwable ignore) {} - break; - } - } - } - } - } - - public void optimize() throws Throwable { -// pool.insertClassPath(new ClassClassPath(PGZIPOutputStream.class)); -// pool.insertClassPath(new ClassClassPath(PGZIPBlock.class)); -// pool.insertClassPath(new ClassClassPath(PGZIPState.class)); -// pool.insertClassPath(new ClassClassPath(PGZIPThreadLocal.class)); -// pool.insertClassPath(new ClassClassPath(ClassPath.class)); -// pool.insertClassPath(new ClassClassPath(CompressionOptimizer.class)); - pool.insertClassPath(new LoaderClassPath(this.getClass().getClassLoader())); - pool.get("com.boydti.fawe.object.io.PGZIPOutputStream").toClass(); - pool.get("com.boydti.fawe.object.io.PGZIPBlock").toClass(); - pool.get("com.boydti.fawe.object.io.PGZIPState").toClass(); - pool.get("com.boydti.fawe.object.io.PGZIPThreadLocal").toClass(); - pool.get("com.boydti.fawe.bukkit.v1_11.compression.CompressionOptimizer").toClass(); - pool.get("javassist.ClassPath").toClass(); - pool.importPackage("net.minecraft.server.v1_11_R1"); - pool.importPackage("java.lang"); - pool.importPackage("java.lang.reflect"); - pool.importPackage("java.io"); - pool.importPackage("com.boydti.fawe.bukkit.v1_11.compression"); -// RegionFile.class.getDeclaredClasses()[0]; - - - { // Optimize NBTCompressedStreamTools - CtClass clazz = pool.get("net.minecraft.server.v1_11_R1.NBTCompressedStreamTools"); - CtMethod methodA_getStream = clazz.getDeclaredMethod("a", new CtClass[]{pool.get("net.minecraft.server.v1_11_R1.NBTTagCompound"), pool.get("java.io.OutputStream")}); - methodA_getStream.setBody("{" + - "java.io.DataOutputStream dataoutputstream = new java.io.DataOutputStream(new java.io.BufferedOutputStream(new com.boydti.fawe.object.io.PGZIPOutputStream($2)));" + - "try {" + - " a($1, (java.io.DataOutput) dataoutputstream);" + - "} finally {" + - " dataoutputstream.close();" + - "}" + - "}"); - clazz.toClass(); - } - - { // Optimize RegionFile - CtClass clazz = pool.get("net.minecraft.server.v1_11_R1.RegionFile"); - CtMethod methodB_getStream = clazz.getDeclaredMethod("b", new CtClass[]{CtClass.intType, CtClass.intType}); - methodB_getStream.setBody("{" + - "Constructor constructor = $0.getClass().getDeclaredClasses()[0].getConstructors()[0];" + - " constructor.setAccessible(true);" + - " return $0.d($1, $2) ? null : new java.io.DataOutputStream(new java.io.BufferedOutputStream(new com.boydti.fawe.object.io.PGZIPOutputStream((OutputStream) CompressionOptimizer.newInstance(constructor, $0, $1, $2))));" + - "}"); - clazz.toClass(); - -// RegionFile $0 = null; -// int $1 = 0; -// int $2 = 0; -// -// Constructor constructor = $0.getClass().getDeclaredClasses()[0].getConstructors()[0]; -// constructor.setAccessible(true); -// return $0.d($1, $2) ? null : new java.io.DataOutputStream(new java.io.BufferedOutputStream(new com.boydti.fawe.object.io.PGZIPOutputStream((OutputStream) constructor.newInstance($1, $2)))); - } - } - - public static Object newInstance(Constructor constructor, RegionFile file, int a, int b) throws IllegalAccessException, InvocationTargetException, InstantiationException { - return constructor.newInstance(file, a, b); - } -} diff --git a/core/build.gradle b/core/build.gradle index 9643869a..7783633a 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,3 +1,6 @@ +repositories { + flatDir {dirs 'lib'} +} dependencies { testCompile 'junit:junit:4.12' compile 'org.yaml:snakeyaml:1.16' @@ -7,8 +10,8 @@ dependencies { compile group: "com.plotsquared", name: "plotsquared-api", version: "latest", changing: true compile 'org.primesoft:BlocksHub:2.0' compile 'com.github.luben:zstd-jni:1.1.1' - compile 'org.javassist:javassist:3.22.0-CR1' - compile 'co.aikar:fastutil-lite:1.0' +// compile 'org.javassist:javassist:3.22.0-CR1' + compile 'it.unimi.dsi:fastutil:6.5.1' compile(group: 'com.sk89q.worldedit', name: 'worldedit-core', version:'6.1.3-SNAPSHOT') { exclude(module: 'bukkit-classloader-check') } diff --git a/core/src/main/java/com/boydti/fawe/FaweAPI.java b/core/src/main/java/com/boydti/fawe/FaweAPI.java index 170bc096..d89c6acb 100644 --- a/core/src/main/java/com/boydti/fawe/FaweAPI.java +++ b/core/src/main/java/com/boydti/fawe/FaweAPI.java @@ -533,11 +533,7 @@ public class FaweAPI { final int i = i2 + x; final int xx = x_offset + x; final short id = (short) (ids[i] & 0xFF); - if (FaweCache.hasData(id)) { - queue.setBlock(xx, yy, zz, id, datas[i]); - } else { - queue.setBlock(xx, yy, zz, id, (byte) 0); - } + queue.setBlock(xx, yy, zz, id, datas[i]); } } } 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 2cd36281..8878d77c 100644 --- a/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java +++ b/core/src/main/java/com/boydti/fawe/command/AnvilCommands.java @@ -5,8 +5,11 @@ import com.boydti.fawe.config.BBC; import com.boydti.fawe.jnbt.anvil.MCAChunk; import com.boydti.fawe.jnbt.anvil.MCAFilter; import com.boydti.fawe.jnbt.anvil.MCAQueue; +import com.boydti.fawe.object.FaweQueue; import com.boydti.fawe.object.mask.FaweBlockMatcher; import com.boydti.fawe.object.number.LongAdder; +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; @@ -16,9 +19,12 @@ import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.function.pattern.RandomPattern; import com.sk89q.worldedit.util.command.binding.Switch; import com.sk89q.worldedit.util.command.parametric.Optional; import java.io.File; +import java.util.ArrayList; +import java.util.List; import java.util.Set; @@ -60,61 +66,107 @@ public class AnvilCommands { 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); - final LongAdder count = new LongAdder(); queue.filterWorld(new MCAFilter() { @Override public void applyBlock(int x, int y, int z, BaseBlock block) { - if (matchFrom.apply(block) && matchTo.apply(block)) { - count.add(1); - } + if (matchFrom.apply(block)) matchTo.apply(block); } }); - player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(count.longValue())); + player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(-1)); } @Command( aliases = {"/replaceallpattern", "/reap", "/repallpat"}, usage = " [from-block] ", desc = "Replace all blocks in the selection with another", - flags = "d", + flags = "dm", min = 2, max = 4 ) @CommandPermissions("worldedit.anvil.replaceall") - public void replaceAllPattern(Player player, EditSession editSession, String folder, @Optional String from, final Pattern to, @Switch('d') boolean useData) throws WorldEditException { - final FaweBlockMatcher matchFrom; - 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); - } - File root = new File(folder + File.separator + "region"); - MCAQueue queue = new MCAQueue(folder, root, true); - final LongAdder count = new LongAdder(); - queue.filterWorld(new MCAFilter() { - private final MutableBlockVector mutable = new MutableBlockVector(0, 0, 0); - - @Override - public void applyBlock(int x, int y, int z, BaseBlock block) { - if (matchFrom.apply(block)) { - mutable.mutX(x); - mutable.mutY(y); - mutable.mutZ(z); - BaseBlock newBlock = to.apply(mutable); - int currentId = block.getId(); - if (FaweCache.hasNBT(currentId)) { - block.setNbtData(null); + 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()); + 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; + } + } + } } - block.setId(newBlock.getId()); - block.setData(newBlock.getData()); - count.add(1); + queue.filterWorld(new MCAFilter() { + private final MutableBlockVector mutable = new MutableBlockVector(0, 0, 0); + @Override + public void applyBlock(int x, int y, int z, BaseBlock block) { + 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"); } + } else { + player.print(BBC.getPrefix() + "Must be a pattern list!"); } - }); - player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(count.longValue())); + } else { + final FaweBlockMatcher matchFrom; + 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); + } + queue.filterWorld(new MCAFilter() { + @Override + public void applyBlock(int x, int y, int z, BaseBlock block) { + 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()); + } + } + }); + } + player.print(BBC.getPrefix() + BBC.VISITOR_BLOCK.format(-1)); } @Command( 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 00622966..85997c4a 100644 --- a/core/src/main/java/com/boydti/fawe/config/BBC.java +++ b/core/src/main/java/com/boydti/fawe/config/BBC.java @@ -123,6 +123,8 @@ public enum BBC { BRUSH_SPHERE("Sphere brush shape equipped (%s0).", "WorldEdit.Brush"), BRUSH_SCATTER("Scatter brush shape equipped (%s0, %s1).", "WorldEdit.Brush"), BRUSH_SHATTER("Shatter brush shape equipped (%s0, %s1).", "WorldEdit.Brush"), + BRUSH_POPULATE("Populate brush shape equipped (%s0, %s1).", "WorldEdit.Brush"), + BRUSH_LAYER("Layer brush shape equipped (%s0, %s1).", "WorldEdit.Brush"), BRUSH_STENCIL("Stencil brush equipped (%s0).", "WorldEdit.Brush"), BRUSH_LINE("Line brush shape equipped (%s0).", "WorldEdit.Brush"), BRUSH_SPLINE("Spline brush shape equipped (%s0). Right click an end to add a shape", "WorldEdit.Brush"), diff --git a/core/src/main/java/com/boydti/fawe/config/Commands.java b/core/src/main/java/com/boydti/fawe/config/Commands.java index d0165335..b4afb41e 100644 --- a/core/src/main/java/com/boydti/fawe/config/Commands.java +++ b/core/src/main/java/com/boydti/fawe/config/Commands.java @@ -9,6 +9,7 @@ import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; public class Commands { @@ -36,6 +37,15 @@ public class Commands { return new TranslatedCommand(command); } + public static String getAlias(String command) { + if (cmdConfig == null) { + return command; + } + ConfigurationSection commands = cmdConfig.getConfigurationSection(command); + List aliases = commands.getStringList("aliases"); + return (aliases == null || aliases.isEmpty()) ? command : aliases.get(0); + } + public static class TranslatedCommand implements Command { private final String[] aliases; private final String usage; diff --git a/core/src/main/java/com/boydti/fawe/example/NMSRelighter.java b/core/src/main/java/com/boydti/fawe/example/NMSRelighter.java index 31d355c6..0d23dd9d 100644 --- a/core/src/main/java/com/boydti/fawe/example/NMSRelighter.java +++ b/core/src/main/java/com/boydti/fawe/example/NMSRelighter.java @@ -206,7 +206,7 @@ public class NMSRelighter implements Relighter{ currentMap.put((int) MathMan.tripleBlockCoord(x, y, z), present); } - public synchronized void fixLightingSafe(boolean sky) { + public void fixLightingSafe(boolean sky) { try { if (sky) { fixSkyLighting(); diff --git a/core/src/main/java/com/boydti/fawe/jnbt/CorruptSchematicStreamer.java b/core/src/main/java/com/boydti/fawe/jnbt/CorruptSchematicStreamer.java index 16e3a391..4a04a769 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/CorruptSchematicStreamer.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/CorruptSchematicStreamer.java @@ -14,7 +14,7 @@ import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.regions.CuboidRegion; -import java.io.BufferedInputStream; +import it.unimi.dsi.fastutil.io.FastBufferedInputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; @@ -49,7 +49,7 @@ public class CorruptSchematicStreamer { try { stream.reset(); stream.mark(Integer.MAX_VALUE); - DataInputStream dataInput = new DataInputStream(new BufferedInputStream(new GZIPInputStream(stream))); + DataInputStream dataInput = new DataInputStream(new FastBufferedInputStream(new GZIPInputStream(stream))); byte[] match = matchTag.getBytes(); int[] matchValue = new int[match.length]; int matchIndex = 0; diff --git a/core/src/main/java/com/boydti/fawe/jnbt/SchematicStreamer.java b/core/src/main/java/com/boydti/fawe/jnbt/SchematicStreamer.java index c943584b..830bd8bd 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/SchematicStreamer.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/SchematicStreamer.java @@ -27,7 +27,6 @@ public class SchematicStreamer extends NBTStreamer { } public void addBlockReaders() { - final long start = System.currentTimeMillis(); NBTStreamReader initializer = new NBTStreamReader() { @Override public void run(Integer length, Integer type) { 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 new file mode 100644 index 00000000..8fbd82ac --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/HeightMapMCAGenerator.java @@ -0,0 +1,940 @@ +package com.boydti.fawe.jnbt.anvil; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.object.PseudoRandom; +import com.boydti.fawe.object.io.BufferedRandomAccessFile; +import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.MathMan; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MutableBlockVector; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.pattern.BlockPattern; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.world.biome.BaseBiome; +import com.sk89q.worldedit.world.registry.WorldData; +import it.unimi.dsi.fastutil.chars.Char2CharMap; +import it.unimi.dsi.fastutil.chars.Char2CharOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; +import java.util.zip.Deflater; + +public class HeightMapMCAGenerator implements Extent { + private final MutableBlockVector mutable = new MutableBlockVector(); + private final ForkJoinPool pool = new ForkJoinPool(); + + final Int2ObjectOpenHashMap blocks = new Int2ObjectOpenHashMap<>(); + + private final byte[] heights; + private final byte[] biomes; + private final char[] floor; + private final char[] main; + private char[] overlay; + + private final File folder; + private final int length; + private final int width; + private final int area; + + private boolean modifiedMain = false; + + public HeightMapMCAGenerator(BufferedImage img, File regionFolder) { + if (!regionFolder.exists()) { + regionFolder.mkdirs(); + } + this.folder = regionFolder; + this.width = img.getWidth(); + this.length = img.getHeight(); + this.area = width * length; + heights = new byte[area]; + biomes = new byte[area]; + floor = new char[area]; + main = new char[area]; + char stone = (char) FaweCache.getCombined(1, 0); + char grass = (char) FaweCache.getCombined(2, 0); + Arrays.fill(main, stone); + Arrays.fill(floor, grass); + int index = 0; + for (int z = 0; z < length; z++) { + for (int x = 0; x < width; x++, index++){ + heights[index] = (byte) img.getRGB(x, z); + } + } + } + + public void addCaves() throws WorldEditException { + CuboidRegion region = new CuboidRegion(new Vector(0, 0, 0), new Vector(width, 255, length)); + addCaves(region); + } + + public void addSchems(Mask mask, WorldData worldData, ClipboardHolder[] clipboards, int rarity, boolean rotate) throws WorldEditException{ + CuboidRegion region = new CuboidRegion(new Vector(0, 0, 0), new Vector(width, 255, length)); + addSchems(region, mask, worldData, clipboards, rarity, rotate); + } + + public void addOre(Mask mask, Pattern material, int size, int frequency, int rarity, int minY, int maxY) throws WorldEditException { + CuboidRegion region = new CuboidRegion(new Vector(0, 0, 0), new Vector(width, 255, length)); + addOre(region, mask, material, size, frequency, rarity, minY, maxY); + } + + public void addDefaultOres(Mask mask) throws WorldEditException { + addOres(new CuboidRegion(new Vector(0, 0, 0), new Vector(width, 255, length)), mask); + } + + @Override + public Vector getMinimumPoint() { + return new Vector(0, 0, 0); + } + + @Override + public Vector getMaximumPoint() { + return new Vector(width - 1, 255, length - 1); + } + + @Override + public boolean setBlock(Vector position, BaseBlock block) throws WorldEditException { + return setBlock(position.getBlockX(), position.getBlockY(), position.getBlockZ(), block); + } + + @Override + public boolean setBiome(Vector2D position, BaseBiome biome) { + int index = position.getBlockZ() * width + position.getBlockX(); + if (index < 0 || index >= heights.length) return false; + biomes[index] = (byte) biome.getId(); + return true; + } + + public int count; + + @Override + public boolean setBlock(int x, int y, int z, BaseBlock block) throws WorldEditException { + count++; + int index = z * width + x; + if (index < 0 || index >= heights.length) return false; + int height = heights[index] & 0xFF; + char combined = (char) FaweCache.getCombined(block); + if (y > height) { + if (y == height + 1) { + if (overlay == null) { + overlay = new char[area]; + } + overlay[index] = combined; + return true; + } + } else if (y == height) { + floor[index] = combined; + return true; + } + short chunkX = (short) (x >> 4); + short chunkZ = (short) (z >> 4); + int pair = MathMan.pair(chunkX, chunkZ); + Char2CharOpenHashMap map = blocks.get(pair); + if (map == null) { + map = new Char2CharOpenHashMap(); + blocks.put(pair, map); + } + char blockPair = (char) (((y >> 4) << 12) + (x & 15) + ((z & 15) << 4) + ((y & 15) << 8)); + map.put(blockPair, combined != 0 ? combined : 1); + return true; + } + + @Override + public BaseBiome getBiome(Vector2D position) { + int index = position.getBlockZ() * width + position.getBlockX(); + if (index < 0 || index >= heights.length) return EditSession.nullBiome; + return FaweCache.CACHE_BIOME[biomes[index] & 0xFF]; + } + + @Override + public BaseBlock getBlock(Vector position) { + return getLazyBlock(position); + } + + @Override + public BaseBlock getLazyBlock(Vector position) { + return getLazyBlock(position.getBlockX(), position.getBlockY(), position.getBlockZ()); + } + + @Override + public BaseBlock getLazyBlock(int x, int y, int z) { + int index = z * width + x; + if (index < 0 || index >= heights.length) return EditSession.nullBlock; + int height = heights[index] & 0xFF; + if (y > height) { + if (y == height + 1) { + return FaweCache.CACHE_BLOCK[overlay != null ? overlay[index] : 0]; + } + if (!blocks.isEmpty()) { + short chunkX = (short) (x >> 4); + short chunkZ = (short) (z >> 4); + int pair = MathMan.pair(chunkX, chunkZ); + Char2CharOpenHashMap map = blocks.get(pair); + if (map != null) { + char blockPair = (char)(((y >> 4) << 12) + (x & 15) + ((z & 15) << 4) + ((y & 15) << 8)); + char combined = map.get(blockPair); + if (combined != 0) { + return FaweCache.CACHE_BLOCK[combined]; + } + } + } + return FaweCache.CACHE_BLOCK[0]; + } else if (y == height) { + return FaweCache.CACHE_BLOCK[floor[index]]; + } else { + if (!blocks.isEmpty()) { + short chunkX = (short) (x >> 4); + short chunkZ = (short) (z >> 4); + int pair = MathMan.pair(chunkX, chunkZ); + Char2CharOpenHashMap map = blocks.get(pair); + if (map != null) { + char blockPair = (char)(((y >> 4) << 12) + (x & 15) + ((z & 15) << 4) + ((y & 15) << 8)); + char combined = map.get(blockPair); + if (combined != 0) { + return FaweCache.CACHE_BLOCK[combined]; + } + } + } + return FaweCache.CACHE_BLOCK[main[index]]; + } + } + + @Override + public int getNearestSurfaceLayer(int x, int z, int y, int minY, int maxY) { + int index = z * width + x; + if (index < 0 || index >= heights.length) return y; + return ((heights[index] & 0xFF) << 3) + (floor[index] & 0xFF) + 1; + } + + @Override + public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY) { + int index = z * width + x; + if (index < 0 || index >= heights.length) return y; + return heights[index] & 0xFF; + } + + public void setBiome(BufferedImage img, byte biome, boolean white) { + if (img.getWidth() != width || img.getHeight() != length) throw new IllegalArgumentException("Input image dimensions do not match the current height map!"); + int index = 0; + for (int z = 0; z < length; z++) { + for (int x = 0; x < width; x++, index++){ + int height = img.getRGB(x, z) & 0xFF; + if (height == 255 || height > 0 && white && PseudoRandom.random.nextInt(256) <= height) { + biomes[index] = biome; + } + } + } + } + + private void setOverlay(BufferedImage img, char combined, boolean white) { + if (img.getWidth() != width || img.getHeight() != length) throw new IllegalArgumentException("Input image dimensions do not match the current height map!"); + if (overlay == null) overlay = new char[area]; + int index = 0; + for (int z = 0; z < length; z++) { + for (int x = 0; x < width; x++, index++){ + int height = img.getRGB(x, z) & 0xFF; + if (height == 255 || height > 0 && white && PseudoRandom.random.nextInt(256) <= height) { + overlay[index] = combined; + } + } + } + } + + private void setMain(BufferedImage img, char combined, boolean white) { + if (img.getWidth() != width || img.getHeight() != length) throw new IllegalArgumentException("Input image dimensions do not match the current height map!"); + modifiedMain = true; + int index = 0; + for (int z = 0; z < length; z++) { + for (int x = 0; x < width; x++, index++){ + int height = img.getRGB(x, z) & 0xFF; + if (height == 255 || height > 0 && white && PseudoRandom.random.nextInt(256) <= height) { + main[index] = combined; + } + } + } + } + + private void setFloor(BufferedImage img, char combined, boolean white) { + if (img.getWidth() != width || img.getHeight() != length) throw new IllegalArgumentException("Input image dimensions do not match the current height map!"); + int index = 0; + for (int z = 0; z < length; z++) { + for (int x = 0; x < width; x++, index++){ + int height = img.getRGB(x, z) & 0xFF; + if (height == 255 || height > 0 && white && PseudoRandom.random.nextInt(256) <= height) { + floor[index] = combined; + } + } + } + } + + private void setColumn(BufferedImage img, char combined, boolean white) { + if (img.getWidth() != width || img.getHeight() != length) throw new IllegalArgumentException("Input image dimensions do not match the current height map!"); + modifiedMain = true; + int index = 0; + for (int z = 0; z < length; z++) { + for (int x = 0; x < width; x++, index++){ + int height = img.getRGB(x, z) & 0xFF; + if (height == 255 || height > 0 && white && PseudoRandom.random.nextInt(256) <= height) { + main[index] = combined; + floor[index] = combined; + } + } + } + } + + public void setBiome(Mask mask, byte biome) { + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++){ + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + if (mask.test(mutable)) { + biomes[index] = biome; + } + } + } + } + + private void setOverlay(Mask mask, char combined) { + int index = 0; + if (overlay == null) overlay = new char[area]; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++){ + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + if (mask.test(mutable)) { + overlay[index] = combined; + } + } + } + } + + private void setFloor(Mask mask, char combined) { + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++){ + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + if (mask.test(mutable)) { + floor[index] = combined; + } + } + } + } + + private void setMain(Mask mask, char combined) { + modifiedMain = true; + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++){ + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + if (mask.test(mutable)) { + main[index] = combined; + } + } + } + } + + private void setColumn(Mask mask, char combined) { + modifiedMain = true; + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++){ + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + if (mask.test(mutable)) { + floor[index] = combined; + main[index] = combined; + } + } + } + } + + public void setOverlay(BufferedImage img, Pattern pattern, boolean white) { + if (pattern instanceof BlockPattern) { + setOverlay(img, (char) ((BlockPattern) pattern).getBlock().getCombined(), white); + return; + } + if (img.getWidth() != width || img.getHeight() != length) throw new IllegalArgumentException("Input image dimensions do not match the current height map!"); + if (overlay == null) overlay = new char[area]; + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++){ + int height = img.getRGB(x, z) & 0xFF; + if (height == 255 || height > 0 && white && PseudoRandom.random.nextInt(256) <= height) { + mutable.mutX(x); + mutable.mutY(height); + overlay[index] = (char) pattern.apply(mutable).getCombined(); + } + } + } + } + + public void setMain(BufferedImage img, Pattern pattern, boolean white) { + if (pattern instanceof BlockPattern) { + setMain(img, (char) ((BlockPattern) pattern).getBlock().getCombined(), white); + return; + } + if (img.getWidth() != width || img.getHeight() != length) throw new IllegalArgumentException("Input image dimensions do not match the current height map!"); + modifiedMain = true; + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++){ + int height = img.getRGB(x, z) & 0xFF; + if (height == 255 || height > 0 && white && PseudoRandom.random.nextInt(256) <= height) { + mutable.mutX(x); + mutable.mutY(height); + main[index] = (char) pattern.apply(mutable).getCombined(); + } + } + } + } + + public void setFloor(BufferedImage img, Pattern pattern, boolean white) { + if (pattern instanceof BlockPattern) { + setFloor(img, (char) ((BlockPattern) pattern).getBlock().getCombined(), white); + return; + } + if (img.getWidth() != width || img.getHeight() != length) throw new IllegalArgumentException("Input image dimensions do not match the current height map!"); + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++){ + int height = img.getRGB(x, z) & 0xFF; + if (height == 255 || height > 0 && white && PseudoRandom.random.nextInt(256) <= height) { + mutable.mutX(x); + mutable.mutY(height); + floor[index] = (char) pattern.apply(mutable).getCombined(); + } + } + } + } + + public void setColumn(BufferedImage img, Pattern pattern, boolean white) { + if (pattern instanceof BlockPattern) { + setColumn(img, (char) ((BlockPattern) pattern).getBlock().getCombined(), white); + return; + } + if (img.getWidth() != width || img.getHeight() != length) throw new IllegalArgumentException("Input image dimensions do not match the current height map!"); + modifiedMain = true; + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++){ + int height = img.getRGB(x, z) & 0xFF; + if (height == 255 || height > 0 && white && PseudoRandom.random.nextInt(256) <= height) { + mutable.mutX(x); + mutable.mutY(height); + char combined = (char) pattern.apply(mutable).getCombined(); + main[index] = combined; + floor[index] = combined; + } + } + } + } + + public void setOverlay(Mask mask, Pattern pattern) { + if (pattern instanceof BlockPattern) { + setOverlay(mask, (char) ((BlockPattern) pattern).getBlock().getCombined()); + return; + } + int index = 0; + if (overlay == null) overlay = new char[area]; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++){ + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + if (mask.test(mutable)) { + overlay[index] = (char) pattern.apply(mutable).getCombined(); + } + } + } + } + + public void setFloor(Mask mask, Pattern pattern) { + if (pattern instanceof BlockPattern) { + setFloor(mask, (char) ((BlockPattern) pattern).getBlock().getCombined()); + return; + } + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++){ + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + if (mask.test(mutable)) { + floor[index] = (char) pattern.apply(mutable).getCombined(); + } + } + } + } + + public void setMain(Mask mask, Pattern pattern) { + if (pattern instanceof BlockPattern) { + setMain(mask, (char) ((BlockPattern) pattern).getBlock().getCombined()); + return; + } + modifiedMain = true; + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++){ + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + if (mask.test(mutable)) { + main[index] = (char) pattern.apply(mutable).getCombined(); + } + } + } + } + + public void setColumn(Mask mask, Pattern pattern) { + if (pattern instanceof BlockPattern) { + setColumn(mask, (char) ((BlockPattern) pattern).getBlock().getCombined()); + return; + } + modifiedMain = true; + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++){ + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + if (mask.test(mutable)) { + char combined = (char) pattern.apply(mutable).getCombined(); + floor[index] = combined; + main[index] = combined; + } + } + } + } + + public void setBiome(int biome) { + Arrays.fill(biomes, (byte) biome); + } + + private void setFloor(int value) { + Arrays.fill(floor, (char) value); + } + + private void setColumn(int value) { + setFloor(value); + setMain(value); + } + + private void setMain(int value) { + modifiedMain = true; + Arrays.fill(main, (char) value); + } + + private void setOverlay(int value) { + if (overlay == null) overlay = new char[area]; + Arrays.fill(overlay, (char) value); + } + + public void setFloor(Pattern value) { + if (value instanceof BlockPattern) { + setFloor(((BlockPattern) value).getBlock().getCombined()); + return; + } + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++) { + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + floor[index] = (char) value.apply(mutable).getCombined(); + } + } + } + + public void setColumn(Pattern value) { + if (value instanceof BlockPattern) { + setColumn(((BlockPattern) value).getBlock().getCombined()); + return; + } + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++) { + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + char combined = (char) value.apply(mutable).getCombined(); + main[index] = combined; + floor[index] = combined; + } + } + } + + public void setMain(Pattern value) { + if (value instanceof BlockPattern) { + setMain(((BlockPattern) value).getBlock().getCombined()); + return; + } + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++) { + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + main[index] = (char) value.apply(mutable).getCombined(); + } + } + } + + public void setOverlay(Pattern value) { + if (overlay == null) overlay = new char[area]; + if (value instanceof BlockPattern) { + setOverlay(((BlockPattern) value).getBlock().getCombined()); + return; + } + int index = 0; + for (int z = 0; z < length; z++) { + mutable.mutZ(z); + for (int x = 0; x < width; x++, index++) { + int y = heights[index] & 0xFF; + mutable.mutX(x); + mutable.mutY(y); + overlay[index] = (char) value.apply(mutable).getCombined(); + } + } + } + + public void setHeights(int value) { + Arrays.fill(heights, (byte) value); + } + + public void generate() throws IOException { + int bcx = 0; + int bcz = 0; + int tcx = (width - 1) >> 4; + int tcz = (length - 1) >> 4; + final ThreadLocal chunkStore = new ThreadLocal() { + @Override + protected MCAChunk initialValue() { + MCAChunk chunk = new MCAChunk(null, 0, 0); + chunk.biomes = new byte[256]; + return chunk; + } + }; + final ThreadLocal byteStore1 = new ThreadLocal() { + @Override + protected byte[] initialValue() { + return new byte[500000]; + } + }; + final ThreadLocal byteStore2 = new ThreadLocal() { + @Override + protected byte[] initialValue() { + return new byte[500000]; + } + }; + final ThreadLocal deflateStore = new ThreadLocal() { + @Override + protected Deflater initialValue() { + Deflater deflater = new Deflater(1, false); + return deflater; + } + }; + final ThreadLocal indexStore = new ThreadLocal() { + @Override + protected int[] initialValue() { + return new int[256]; + } + }; + boolean hasOverlay = this.overlay != null; + byte[] fileBuf = new byte[1 << 16]; + for (int mcaZ = 0; mcaZ <= (length >> 9); mcaZ++) { + for (int mcaX = 0; mcaX <= (width >> 9); mcaX++) { + final int fmcaX = mcaX; + final int fmcaZ = mcaZ; + File file = new File(folder, "r." + mcaX + "." + mcaZ + ".mca"); + if (!file.exists()) { + file.createNewFile(); + } + final BufferedRandomAccessFile raf = new BufferedRandomAccessFile(file, "rw", fileBuf); + final byte[] header = new byte[4096]; + final byte[][] compressed = new byte[1024][]; + int bx = mcaX << 9; + int bz = mcaZ << 9; + int scx = bx >> 4; + 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); + for (int cx = scx; cx <= ecx; cx++) { + final int csx = cx << 4; + final int cex = Math.min(csx + 15, width - 1); + final int fcx = cx; + final int fcz = cz; + int chunkPair = MathMan.pair((short) cx, (short) cz); + final Char2CharOpenHashMap localBlocks = blocks.get(chunkPair); + pool.submit(new Runnable() { + @Override + public void run() { + try { + MCAChunk chunk = chunkStore.get(); + int[] indexes = indexStore.get(); + for (byte[] array : chunk.ids) { + if (array != null) { + Arrays.fill(array, (byte) 0); + } + } + int index = 0; + int maxY = 0; + int minY = Integer.MAX_VALUE; + int[] heightMap = chunk.getHeightMapArray(); + int globalIndex; + for (int z = csz; z <= cez; z++) { + globalIndex = z * width + csx; + for (int x = csx; x <= cex; x++, index++, globalIndex++) { + indexes[index] = globalIndex; + int height = heights[globalIndex] & 0xFF; + heightMap[index] = height; + maxY = Math.max(maxY, height); + minY = Math.min(minY, height); + } + } + if (hasOverlay) { + maxY++; + } + int maxLayer = maxY >> 4; + int fillLayers = Math.max(0, (minY - 1)) >> 4; + for (int layer = 0; layer <= maxLayer; layer++) { + if (chunk.ids[layer] == null) { + chunk.ids[layer] = new byte[4096]; + chunk.data[layer] = new byte[2048]; + chunk.skyLight[layer] = new byte[2048]; + chunk.blockLight[layer] = new byte[2048]; + } + } + if (modifiedMain) { // If the main block is modified, we can't short circuit this + for (int layer = 0; layer < fillLayers; layer++) { + index = 0; + byte[] layerIds = chunk.ids[layer]; + byte[] layerDatas = chunk.data[layer]; + for (int z = csz; z <= cez; z++) { + for (int x = csx; x <= cex; x++, index++) { + globalIndex = indexes[index]; + char mainCombined = main[globalIndex]; + byte id = (byte) FaweCache.getId(mainCombined); + int data = FaweCache.getData(mainCombined); + if (data != 0) { + for (int y = 0; y < 16; y++) { + int mainIndex = index + (y << 8); + chunk.setNibble(mainIndex, layerDatas, data); + } + } + for (int y = 0; y < 16; y++) { + layerIds[index + (y << 8)] = id; + } + } + } + } + } else { + for (int layer = 0; layer < fillLayers; layer++) { + Arrays.fill(chunk.ids[layer], (byte) 1); + } + } + for (int layer = fillLayers; layer <= maxLayer; layer++) { + Arrays.fill(chunk.skyLight[layer], (byte) 255); + byte[] layerIds = chunk.ids[layer]; + byte[] layerDatas = chunk.data[layer]; + index = 0; + int startY = layer << 4; + int endY = startY + 15; + for (int z = csz; z <= cez; z++) { + for (int x = csx; x <= cex; x++, index++) { + globalIndex = indexes[index]; + int height = heightMap[index]; + int diff; + if (height > endY) { + diff = 16; + } else if (height >= startY) { + diff = height - startY; + char floorCombined = floor[globalIndex]; + int id = FaweCache.getId(floorCombined); + int floorIndex = index + ((height & 15) << 8); + layerIds[floorIndex] = (byte) id; + int data = FaweCache.getData(floorCombined); + if (data != 0) { + chunk.setNibble(floorIndex, layerDatas, data); + } + if (hasOverlay && height >= startY - 1 && height < endY) { + char overlayCombined = overlay[globalIndex]; + id = FaweCache.getId(overlayCombined); + int overlayIndex = index + (((height + 1) & 15) << 8); + layerIds[overlayIndex] = (byte) id; + data = FaweCache.getData(overlayCombined); + if (data != 0) { + chunk.setNibble(overlayIndex, layerDatas, data); + } + } + } else if (hasOverlay && height == startY - 1) { + char overlayCombined = overlay[globalIndex]; + int id = FaweCache.getId(overlayCombined); + int overlayIndex = index + (((height + 1) & 15) << 8); + layerIds[overlayIndex] = (byte) id; + int data = FaweCache.getData(overlayCombined); + if (data != 0) { + chunk.setNibble(overlayIndex, layerDatas, data); + } + continue; + } else { + continue; + } + char mainCombined = main[globalIndex]; + byte id = (byte) FaweCache.getId(mainCombined); + int data = FaweCache.getData(mainCombined); + if (data != 0) { + for (int y = 0; y < diff; y++) { + int mainIndex = index + (y << 8); + chunk.setNibble(mainIndex, layerDatas, data); + } + } + for (int y = 0; y < diff; y++) { + layerIds[index + (y << 8)] = id; + } + } + } + } + int maxYMod = 15 + (maxLayer << 4); + for (int layer = (maxY >> 4) + 1; layer < 16; layer++) { + chunk.ids[layer] = null; + } + index = 0; + { // Bedrock + byte[] layerIds = chunk.ids[0]; + for (int z = csz; z <= cez; z++) { + for (int x = csx; x <= cex; x++) { + layerIds[index++] = (byte) 7; + } + } + } + if (localBlocks != null && !localBlocks.isEmpty()) { + for (Char2CharMap.Entry entry : localBlocks.char2CharEntrySet()) { + char key = entry.getCharKey(); + char combined = entry.getCharValue(); + byte id = (byte) FaweCache.getId(combined); + + int and = key & 4095; + int y = ((key >> 12) << 4) + (and >> 8); + int x = and & 0xF; + int z = (and >> 4) & 0xF; + int layer = key >> 12; + int localIndex = key & 4095; + if (!FaweCache.hasData(id)) { + if (chunk.ids[layer] == null) { + chunk.ids[layer] = new byte[4096]; + chunk.data[layer] = new byte[2048]; + chunk.skyLight[layer] = new byte[2048]; + chunk.blockLight[layer] = new byte[2048]; + } + chunk.setIdUnsafe(layer, localIndex, id); + } else { + int data = FaweCache.getData(combined); + chunk.setBlockUnsafe(layer, localIndex, id, data); + } + } + + } + chunk.setLoc(null, fcx, fcz); + byte[] bytes = chunk.toBytes(byteStore1.get()); + byte[] compressedBytes = MainUtil.compress(bytes, byteStore2.get(), deflateStore.get()); + int blocks = (compressed.length + 4095) >> 12; + compressed[((fcx & 31)) + ((fcz & 31) << 5)] = compressedBytes.clone(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + }); + } + } + pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + pool.submit(new Runnable() { + @Override + public void run() { + try { + int totalLength = 8192; + for (int i = 0; i < compressed.length; i++) { + byte[] compressedBytes = compressed[i]; + if (compressedBytes != null) { + int blocks = ((4095 + compressedBytes.length + 5) / 4096) * 4096; + totalLength += blocks; + } + } + raf.setLength(totalLength); + int offset = 8192; + for (int i = 0; i < compressed.length; i++) { + byte[] compressedBytes = compressed[i]; + if (compressedBytes != null) { + // Set header + int index = i << 2; + int offsetMedium = offset >> 12; + int blocks = ((4095 + compressedBytes.length + 5) / 4096); + header[index] = (byte) (offsetMedium >> 16); + header[index + 1] = (byte) ((offsetMedium >> 8)); + header[index + 2] = (byte) ((offsetMedium >> 0)); + header[index + 3] = (byte) (blocks); + // Write bytes + int cx = (fmcaX << 5) + (i & 31); + int cz = (fmcaZ << 5) + (i >> 5); + raf.seek(offset); + raf.writeInt(compressedBytes.length); + raf.write(2); + raf.write(compressedBytes); + offset += blocks * 4096; + } + } + raf.seek(0); + raf.write(header); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + raf.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + }); + } + } + pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + } +} diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java index e61b9957..95eaff34 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAChunk.java @@ -5,11 +5,15 @@ import com.boydti.fawe.jnbt.NBTStreamer; import com.boydti.fawe.object.FaweChunk; import com.boydti.fawe.object.FaweQueue; import com.boydti.fawe.object.RunnableVal2; +import com.boydti.fawe.object.io.FastByteArrayOutputStream; import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.MathMan; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.NBTConstants; import com.sk89q.jnbt.NBTInputStream; +import com.sk89q.jnbt.NBTOutputStream; +import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -19,6 +23,9 @@ import java.util.Map; import java.util.Set; import java.util.UUID; + +import static com.google.common.base.Preconditions.checkNotNull; + public class MCAChunk extends FaweChunk { // ids: byte[16][4096] @@ -47,6 +54,65 @@ public class MCAChunk extends FaweChunk { private boolean modified; private boolean deleted; + public byte[] toBytes(byte[] buffer) throws IOException { + checkNotNull(buffer); + if (buffer == null) { + buffer = new byte[8192]; + } + FastByteArrayOutputStream buffered = new FastByteArrayOutputStream(buffer); + DataOutputStream dataOut = new DataOutputStream(buffered); + NBTOutputStream nbtOut = new NBTOutputStream(dataOut); + nbtOut.writeNamedTagName("", NBTConstants.TYPE_COMPOUND); + nbtOut.writeLazyCompoundTag("Level", new NBTOutputStream.LazyWrite() { + @Override + public void write(NBTOutputStream out) throws IOException { + out.writeNamedTag("V", (byte) 1); + out.writeNamedTag("xPos", getX()); + out.writeNamedTag("zPos", getZ()); + out.writeNamedTag("LightPopulated", (byte) 0); + out.writeNamedTag("TerrainPopulated", (byte) 1); + if (entities.isEmpty()) { + out.writeNamedEmptyList("Entities"); + } else { + out.writeNamedTag("Entities", new ListTag(CompoundTag.class, new ArrayList(entities.values()))); + } + if (tiles.isEmpty()) { + out.writeNamedEmptyList("TileEntities"); + } else { + out.writeNamedTag("TileEntities", new ListTag(CompoundTag.class, new ArrayList(tiles.values()))); + } + out.writeNamedTag("InhabitedTime", inhabitedTime); + out.writeNamedTag("LastUpdate", lastUpdate); + if (biomes != null) { + out.writeNamedTag("Biomes", biomes); + } + out.writeNamedTag("HeightMap", heightMap); + out.writeNamedTagName("Sections", NBTConstants.TYPE_LIST); + dataOut.writeByte(NBTConstants.TYPE_COMPOUND); + int len = 0; + for (int layer = 0; layer < ids.length; layer++) { + if (ids[layer] != null) len++; + } + dataOut.writeInt(len); + for (int layer = 0; layer < ids.length; layer++) { + byte[] idLayer = ids[layer]; + if (idLayer == null) { + continue; + } + out.writeNamedTag("Y", (byte) layer); + out.writeNamedTag("BlockLight", blockLight[layer]); + out.writeNamedTag("SkyLight", skyLight[layer]); + out.writeNamedTag("Blocks", idLayer); + out.writeNamedTag("Data", data[layer]); + out.writeEndTag(); + } + } + }); + nbtOut.writeEndTag(); + nbtOut.close(); + return buffered.toByteArray(); + } + public CompoundTag toTag() { if (deleted) { return null; @@ -87,6 +153,20 @@ public class MCAChunk extends FaweChunk { return FaweCache.asTag(root); } + public MCAChunk(FaweQueue queue, int x, int z) { + super(queue, x, z); + this.ids = new byte[16][]; + this.data = new byte[16][]; + this.skyLight = new byte[16][]; + this.blockLight = new byte[16][]; + this.biomes = new byte[256]; + this.tiles = new HashMap<>(); + this.entities = new HashMap<>(); + this.lastUpdate = System.currentTimeMillis(); + this.heightMap = new int[256]; + this.modified = true; + } + public MCAChunk(MCAChunk parent, boolean shallow) { super(parent.getParent(), parent.getX(), parent.getZ()); if (shallow) { @@ -127,7 +207,6 @@ public class MCAChunk extends FaweChunk { skyLight = new byte[16][]; blockLight = new byte[16][]; this.compressedSize = compressedSize; -// NamedTag tag = nis.readNamedTag(); NBTStreamer streamer = new NBTStreamer(nis); streamer.addReader(".Level.InhabitedTime", new RunnableVal2() { @Override @@ -151,7 +230,7 @@ public class MCAChunk extends FaweChunk { blockLight[layer] = tag.getByteArray("BlockLight"); } }); - streamer.addReader(".Level.Entities.#", new RunnableVal2() { + streamer.addReader(".Level.TileEntities.#", new RunnableVal2() { @Override public void run(Integer index, CompoundTag tile) { int x = tile.getInt("x") & 15; @@ -161,12 +240,13 @@ public class MCAChunk extends FaweChunk { tiles.put(pair, tile); } }); - streamer.addReader(".Level.TileEntities.#", new RunnableVal2() { + streamer.addReader(".Level.Entities.#", new RunnableVal2() { @Override public void run(Integer index, CompoundTag entityTag) { if (entities == null) { entities = new HashMap(); } + long least = entityTag.getLong("UUIDLeast"); long most = entityTag.getLong("UUIDMost"); entities.put(new UUID(most, least), entityTag); @@ -205,7 +285,7 @@ public class MCAChunk extends FaweChunk { } @Deprecated - public void setModified() { + public final void setModified() { this.modified = true; } @@ -375,11 +455,34 @@ public class MCAChunk extends FaweChunk { public void setNibble(int index, byte[] array, int value) { int indexShift = index >> 1; - if((index & 1) == 0) { - array[indexShift] = (byte)(array[indexShift] & 240 | value & 15); - } else { - array[indexShift] = (byte)(array[indexShift] & 15 | (value & 15) << 4); + byte existing = array[indexShift]; + int valueShift = value << 4; + if (existing == value + valueShift) { + return; } + if((index & 1) == 0) { + array[indexShift] = (byte)(existing & 240 | value); + } else { + array[indexShift] = (byte)(existing & 15 | valueShift); + } + } + + public void setIdUnsafe(int layer, int index, byte id) { + byte[] idsLayer = ids[layer]; + idsLayer[index] = id; + } + + public void setBlockUnsafe(int layer, int index, byte id, int data) { + byte[] idsLayer = ids[layer]; + if (idsLayer == null) { + idsLayer = this.ids[layer] = new byte[4096]; + this.data[layer] = new byte[2048]; + this.skyLight[layer] = new byte[2048]; + this.blockLight[layer] = new byte[2048]; + } + idsLayer[index] = id; + byte[] dataLayer = this.data[layer]; + setNibble(index, dataLayer, data); } @Override @@ -399,6 +502,11 @@ public class MCAChunk extends FaweChunk { setNibble(j, dataLayer, data); } + @Override + public void setBiome(byte biome) { + Arrays.fill(biomes, biome); + } + @Override public void removeEntity(UUID uuid) { modified = true; 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 9d75d2fd..aca202ba 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 @@ -1,5 +1,6 @@ package com.boydti.fawe.jnbt.anvil; +import com.boydti.fawe.Fawe; import com.boydti.fawe.jnbt.NBTStreamer; import com.boydti.fawe.object.FaweQueue; import com.boydti.fawe.object.RunnableVal; @@ -8,21 +9,22 @@ import com.boydti.fawe.object.exception.FaweException; import com.boydti.fawe.object.io.BufferedRandomAccessFile; import com.boydti.fawe.object.io.FastByteArrayInputStream; import com.boydti.fawe.object.io.FastByteArrayOutputStream; +import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.MathMan; -import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.NBTInputStream; -import com.sk89q.jnbt.NBTOutputStream; -import java.io.BufferedInputStream; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.io.FastBufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.zip.Deflater; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; import java.util.zip.DeflaterOutputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; @@ -33,24 +35,55 @@ import java.util.zip.InflaterInputStream; */ public class MCAFile { + private static Field fieldBuf2; + private static Field fieldBuf3; + private static Field fieldBuf4; + private static Field fieldBuf5; + private static Field fieldBuf6; + static { + try { + fieldBuf2 = InflaterInputStream.class.getDeclaredField("buf"); + fieldBuf2.setAccessible(true); + fieldBuf3 = NBTInputStream.class.getDeclaredField("buf"); + fieldBuf3.setAccessible(true); + fieldBuf4 = FastByteArrayOutputStream.class.getDeclaredField("buffer"); + fieldBuf4.setAccessible(true); + fieldBuf5 = DeflaterOutputStream.class.getDeclaredField("buf"); + fieldBuf5.setAccessible(true); + fieldBuf6 = BufferedOutputStream.class.getDeclaredField("buf"); + fieldBuf6.setAccessible(true); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + private FaweQueue queue; private File file; private RandomAccessFile raf; private byte[] locations; - private FaweQueue queue; - private Field fieldBuf1; - private Field fieldBuf2; - private Field fieldBuf3; - private Field fieldBuf4; - private Field fieldBuf5; - private Field fieldBuf6; - private byte[] buffer1 = new byte[4096]; - private byte[] buffer2 = new byte[4096]; - private byte[] buffer3 = new byte[720]; + final ThreadLocal byteStore1 = new ThreadLocal() { + @Override + protected byte[] initialValue() { + return new byte[4096]; + } + }; + final ThreadLocal byteStore2 = new ThreadLocal() { + @Override + protected byte[] initialValue() { + return new byte[4096]; + } + }; + final ThreadLocal byteStore3 = new ThreadLocal() { + @Override + protected byte[] initialValue() { + return new byte[1024]; + } + }; private final int X, Z; - private Map chunks = new HashMap<>(); + private Int2ObjectOpenHashMap chunks = new Int2ObjectOpenHashMap<>(); public MCAFile(FaweQueue parent, File file) { this.queue = parent; @@ -71,20 +104,10 @@ public class MCAFile { try { if (raf == null) { this.locations = new byte[4096]; - this.raf = new BufferedRandomAccessFile(file, "rw", (int) file.length()); + this.raf = new RandomAccessFile(file, "rw"); + raf.seek(0); raf.readFully(locations); - fieldBuf1 = BufferedInputStream.class.getDeclaredField("buf"); - fieldBuf1.setAccessible(true); - fieldBuf2 = InflaterInputStream.class.getDeclaredField("buf"); - fieldBuf2.setAccessible(true); - fieldBuf3 = NBTInputStream.class.getDeclaredField("buf"); - fieldBuf3.setAccessible(true); - fieldBuf4 = FastByteArrayOutputStream.class.getDeclaredField("buffer"); - fieldBuf4.setAccessible(true); - fieldBuf5 = DeflaterOutputStream.class.getDeclaredField("buf"); - fieldBuf5.setAccessible(true); - fieldBuf6 = BufferedOutputStream.class.getDeclaredField("buf"); - fieldBuf6.setAccessible(true); + } } catch (Throwable e) { e.printStackTrace(); @@ -109,7 +132,9 @@ public class MCAFile { public MCAChunk getCachedChunk(int cx, int cz) { int pair = MathMan.pair((short) (cx & 31), (short) (cz & 31)); - return chunks.get(pair); + synchronized (chunks) { + return chunks.get(pair); + } } public MCAChunk getChunk(int cx, int cz) throws IOException { @@ -132,10 +157,42 @@ public class MCAFile { MCAChunk chunk = new MCAChunk(nis, queue, cx, cz, size); nis.close(); int pair = MathMan.pair((short) (cx & 31), (short) (cz & 31)); - chunks.put(pair, chunk); + synchronized (chunks) { + chunks.put(pair, chunk); + } return chunk; } + public void forEachSortedChunk(RunnableVal4 onEach) throws IOException { + char[] offsets = new char[(int) (raf.length() / 4096) - 2]; + Arrays.fill(offsets, Character.MAX_VALUE); + char i = 0; + for (int z = 0; z < 32; z++) { + for (int x = 0; x < 32; x++, i += 4) { + int offset = (((locations[i] & 0xFF) << 16) + ((locations[i + 1] & 0xFF) << 8) + ((locations[i+ 2] & 0xFF))) - 2; + int size = locations[i + 3] & 0xFF; + if (size != 0) { + if (offset < offsets.length) { + offsets[offset] = i; + } else { + Fawe.debug("Ignoring invalid offset " + offset); + } + } + } + } + for (i = 0; i < offsets.length; i++) { + int index = offsets[i]; + if (index != Character.MAX_VALUE) { + int offset = i + 2; + int size = locations[index + 3] & 0xFF; + int index2 = index >> 2; + int x = (index2) & 31; + int z = (index2) >> 5; + onEach.run(x, z, offset << 12, size << 12); + } + } + } + /** * @param onEach cx, cz, offset, size */ @@ -188,23 +245,17 @@ public class MCAFile { } private byte[] getChunkCompressedBytes(int offset) throws IOException{ - raf.seek(offset); - int size = raf.readInt(); - int compression = raf.read(); - byte[] data = new byte[size]; - raf.readFully(data); - return data; - } - - private void writeSafe(int offset, byte[] data) throws IOException { - int len = data.length + 5; - raf.seek(offset); - if (raf.length() - offset < len) { - raf.setLength(offset + len); + if (offset == 0) { + return null; + } + synchronized (raf) { + raf.seek(offset); + int size = raf.readInt(); + int compression = raf.read(); + byte[] data = new byte[size]; + raf.readFully(data); + return data; } - raf.writeInt(data.length); - raf.write(2); - raf.write(data); } private NBTInputStream getChunkIS(int offset) throws IOException { @@ -212,11 +263,10 @@ public class MCAFile { byte[] data = getChunkCompressedBytes(offset); FastByteArrayInputStream bais = new FastByteArrayInputStream(data); InflaterInputStream iis = new InflaterInputStream(bais, new Inflater(), 1); - fieldBuf2.set(iis, buffer2); - BufferedInputStream bis = new BufferedInputStream(iis, 1); - fieldBuf1.set(bis, buffer1); + fieldBuf2.set(iis, byteStore2.get()); + FastBufferedInputStream bis = new FastBufferedInputStream(iis, byteStore1.get()); NBTInputStream nis = new NBTInputStream(bis); - fieldBuf3.set(nis, buffer3); + fieldBuf3.set(nis, byteStore3.get()); return nis; } catch (IllegalAccessException unlikely) { unlikely.printStackTrace(); @@ -243,45 +293,33 @@ public class MCAFile { * @param onEach chunk */ public void forEachCachedChunk(RunnableVal onEach) { - for (Map.Entry entry : chunks.entrySet()) { - onEach.run(entry.getValue()); + synchronized (chunks) { + for (Map.Entry entry : chunks.entrySet()) { + onEach.run(entry.getValue()); + } } } public List getCachedChunks() { - return new ArrayList<>(chunks.values()); + synchronized (chunks) { + return new ArrayList<>(chunks.values()); + } } public void uncache(int cx, int cz) { int pair = MathMan.pair((short) (cx & 31), (short) (cz & 31)); - chunks.remove(pair); + synchronized (chunks) { + chunks.remove(pair); + } } private byte[] toBytes(MCAChunk chunk) throws Exception { - CompoundTag tag = chunk.toTag(); - if (tag == null || chunk.isDeleted()) { + if (chunk.isDeleted()) { return null; } - FastByteArrayOutputStream baos = new FastByteArrayOutputStream(buffer3); - -// PGZIPOutputStream deflater = new PGZIPOutputStream(baos); -// deflater.setStrategy(Deflater.FILTERED); - Deflater deflate = new Deflater(1); - deflate.setStrategy(Deflater.FILTERED); - DeflaterOutputStream deflater = new DeflaterOutputStream(baos, deflate, 1, true); - fieldBuf5.set(deflater, buffer2); - BufferedOutputStream bos = new BufferedOutputStream(deflater, 1); - fieldBuf6.set(bos, buffer1); - NBTOutputStream nos = new NBTOutputStream(bos); - nos.writeNamedTag("", tag); - bos.flush(); - bos.close(); - byte[] result = baos.toByteArray(); - baos.close(); - deflater.close(); - bos.close(); - nos.close(); - return result; + byte[] uncompressed = chunk.toBytes(byteStore3.get()); + byte[] compressed = MainUtil.compress(uncompressed, byteStore2.get(), null); + return compressed; } private byte[] getChunkBytes(int cx, int cz) throws Exception{ @@ -296,7 +334,19 @@ public class MCAFile { return toBytes(mca); } - private void writeHeader(int cx, int cz, int offsetMedium, int sizeByte) throws IOException { + + private void writeSafe(RandomAccessFile raf, int offset, byte[] data) throws IOException { + int len = data.length + 5; + raf.seek(offset); + if (raf.length() - offset < len) { + raf.setLength(((offset + len + 4095) / 4096) * 4096); + } + raf.writeInt(data.length); + raf.write(2); + raf.write(data); + } + + private void writeHeader(RandomAccessFile raf, int cx, int cz, int offsetMedium, int sizeByte, boolean writeTime) throws IOException { int i = ((cx & 31) << 2) + ((cz & 31) << 7); raf.seek(i); raf.write((offsetMedium >> 16)); @@ -309,134 +359,150 @@ public class MCAFile { } else { raf.writeInt((int) (System.currentTimeMillis() / 1000L)); } - int offset = (((locations[i] & 0xFF) << 16) + ((locations[i + 1] & 0xFF) << 8) + ((locations[i+ 2] & 0xFF))) << 12; - int size = (locations[i + 3] & 0xFF) << 12; } - public void close() { - flush(); - if (raf != null) { - try { - raf.close(); - } catch (IOException e) { - e.printStackTrace(); - } - file = null; - raf = null; - locations = null; - queue = null; - fieldBuf1 = null; - fieldBuf2 = null; - fieldBuf3 = null; - fieldBuf4 = null; - fieldBuf5 = null; - fieldBuf6 = null; - buffer1 = null; - buffer2 = null; - buffer3 = null; - chunks = null; - } - } - - public void flush() { - boolean modified = false; - for (MCAChunk chunk : getCachedChunks()) { - if (chunk.isModified()) { - modified = true; - break; - } - } - if (!modified) { - return; - } - final HashMap offsetMap = new HashMap<>(); // Offset -> - forEachChunk(new RunnableVal4() { - @Override - public void run(Integer cx, Integer cz, Integer offset, Integer size) { - short pair1 = MathMan.pairByte((byte) (cx & 31), (byte) (cz & 31)); - short pair2 = (short) (size >> 12); - offsetMap.put(offset, MathMan.pair(pair1, pair2)); - } - }); - - HashMap relocate = new HashMap(); - int start = 8192; - int written = start; - int end = 8192; - int nextOffset = 8192; - try { - for (int count = 0; count < offsetMap.size(); count++) { - Integer loc = offsetMap.get(nextOffset); - while (loc == null) { - nextOffset += 4096; - loc = offsetMap.get(nextOffset); + public void close(ForkJoinPool pool) { + synchronized (raf) { + if (raf != null) { + flush(pool); + try { + raf.close(); + } catch (IOException e) { + e.printStackTrace(); } - int offset = nextOffset; - short cxz = MathMan.unpairX(loc); - int cx = MathMan.unpairShortX(cxz); - int cz = MathMan.unpairShortY(cxz); - int size = MathMan.unpairY(loc) << 12; - nextOffset += size; - end += size; - int pair = MathMan.pair((short) (cx & 31), (short) (cz & 31)); - byte[] newBytes = relocate.get(pair); - if (newBytes == null) { - if (offset == start) { - MCAChunk cached = getCachedChunk(cx, cz); - if (cached == null || !cached.isModified()) { - start += size; - written = start + size; + file = null; + raf = null; + locations = null; + queue = null; + chunks = null; + } + } + } + + public void flush(ForkJoinPool pool) { + synchronized (raf) { + boolean wait; + if (pool == null) { + wait = true; + pool = new ForkJoinPool(); + } else wait = false; + Int2ObjectOpenHashMap relocate = new Int2ObjectOpenHashMap<>(); + final Int2ObjectOpenHashMap offsetMap = new Int2ObjectOpenHashMap<>(); // Offset -> + final Int2ObjectOpenHashMap compressedMap = new Int2ObjectOpenHashMap<>(); + boolean modified = false; + for (MCAChunk chunk : getCachedChunks()) { + if (chunk.isModified()) { + modified = true; + if (!chunk.isDeleted()) { + pool.submit(new Runnable() { + @Override + public void run() { + try { + byte[] compressed = toBytes(chunk); + int pair = MathMan.pair((short) (chunk.getX() & 31), (short) (chunk.getZ() & 31)); + synchronized (compressedMap) { + compressedMap.put(pair, compressed); + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + }); + } + } + } + if (modified) { + forEachChunk(new RunnableVal4() { + @Override + public void run(Integer cx, Integer cz, Integer offset, Integer size) { + short pair1 = MathMan.pairByte((byte) (cx & 31), (byte) (cz & 31)); + short pair2 = (short) (size >> 12); + offsetMap.put((int) offset, (Integer) MathMan.pair(pair1, pair2)); + } + }); + pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + int start = 8192; + int written = start; + int end = 8192; + int nextOffset = 8192; + try { + for (int count = 0; count < offsetMap.size(); count++) { + Integer loc = offsetMap.get(nextOffset); + while (loc == null) { + nextOffset += 4096; + loc = offsetMap.get(nextOffset); + } + int offset = nextOffset; + short cxz = MathMan.unpairX(loc); + int cx = MathMan.unpairShortX(cxz); + int cz = MathMan.unpairShortY(cxz); + int size = MathMan.unpairY(loc) << 12; + nextOffset += size; + end = Math.min(start + size, end); + int pair = MathMan.pair((short) (cx & 31), (short) (cz & 31)); + byte[] newBytes = relocate.get(pair); + if (newBytes == null) { + if (offset == start) { + MCAChunk cached = getCachedChunk(cx, cz); + if (cached == null || !cached.isModified()) { + writeHeader(raf, cx, cz, start >> 12, size >> 12, true); + start += size; + written = start + size; + continue; + } else { + newBytes = compressedMap.get(pair); + } + } else { + newBytes = compressedMap.get(pair); + if (newBytes == null) { + newBytes = getChunkCompressedBytes(getOffset(cx, cz)); + } + } + } + if (newBytes == null) { + writeHeader(raf, cx, cz, 0, 0, false); continue; - } else { - newBytes = toBytes(cached); } - } else { - newBytes = getChunkBytes(cx, cz); - } - } - if (newBytes == null) { - writeHeader(cx, cz, 0, 0); - continue; - } - int len = newBytes.length + 5; - int oldSize = (size + 4095) >> 12; - int newSize = (len + 4095) >> 12; - int nextOffset2 = nextOffset; - while (start + len > end) { - Integer nextLoc = offsetMap.get(nextOffset2); - if (nextLoc != null) { - short nextCXZ = MathMan.unpairX(nextLoc); - int nextCX = MathMan.unpairShortX(nextCXZ); - int nextCZ = MathMan.unpairShortY(nextCXZ); - if (getCachedChunk(nextCX, nextCZ) == null) { - byte[] nextBytes = getChunkCompressedBytes(nextOffset2); - relocate.put(pair, nextBytes); + int len = newBytes.length + 5; + int oldSize = (size + 4095) >> 12; + int newSize = (len + 4095) >> 12; + int nextOffset2 = end; + while (start + len > end) { + Integer nextLoc = offsetMap.get(nextOffset2); + if (nextLoc != null) { + short nextCXZ = MathMan.unpairX(nextLoc); + int nextCX = MathMan.unpairShortX(nextCXZ); + int nextCZ = MathMan.unpairShortY(nextCXZ); + if (getCachedChunk(nextCX, nextCZ) == null) { + byte[] nextBytes = getChunkCompressedBytes(nextOffset2); + relocate.put(MathMan.pair((short) (nextCX & 31), (short) (nextCZ & 31)), nextBytes); + } + int nextSize = MathMan.unpairY(nextLoc) << 12; + end += nextSize; + nextOffset2 += nextSize; + } else { + end += 4096; + nextOffset2 += 4096; + } } -// System.out.println("Relocating " + nextCX + "," + nextCZ); - int nextSize = MathMan.unpairY(nextLoc) << 12; - end += nextSize; - nextOffset2 += nextSize; - } else { - end = start + len; - break; + writeSafe(raf, start, newBytes); + writeHeader(raf, cx, cz, start >> 12, newSize, true); + written = start + newBytes.length + 5; + start += newSize << 12; } + raf.setLength(4096 * ((written + 4095) / 4096)); + if (raf instanceof BufferedRandomAccessFile) { + ((BufferedRandomAccessFile) raf).flush(); + } + raf.close(); + } catch (Throwable e) { + e.printStackTrace(); } -// System.out.println("Writing: " + cx + "," + cz); - writeSafe(start, newBytes); - if (offset != start || end != start + size || oldSize != newSize || true) { -// System.out.println("Header: " + cx + "," + cz + " | " + offset + "," + start + " | " + end + "," + (start + size) + " | " + size + " | " + start); - writeHeader(cx, cz, start >> 12, newSize); + if (wait) { + pool.shutdown(); + pool.awaitQuiescence(Long.MAX_VALUE, TimeUnit.MILLISECONDS); } - written = start + newBytes.length + 5; - start += newSize << 12; } - raf.setLength(written); - if (raf instanceof BufferedRandomAccessFile) { - ((BufferedRandomAccessFile) raf).flush(); - } - raf.close(); - } catch (Throwable e) { - e.printStackTrace(); } } } diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueue.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueue.java index 4c3c1a94..a3195474 100644 --- a/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueue.java +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/MCAQueue.java @@ -70,6 +70,12 @@ public class MCAQueue extends NMSMappedFaweQueue blockStore = new ThreadLocal() { + @Override + protected MutableMCABackedBaseBlock initialValue() { + return new MutableMCABackedBaseBlock(); + } + }; for (final File file : folder.listFiles()) { try { String name = file.getName(); @@ -82,18 +88,16 @@ public class MCAQueue extends NMSMappedFaweQueue() { @Override - public void run() { - // May not do anything, but seems to lead to smaller lag spikes - System.gc(); - System.gc(); - final MutableMCABackedBaseBlock mutableBlock = new MutableMCABackedBaseBlock(); - final int cbx = mcaX << 5; - final int cbz = mcaZ << 5; - finalFile.forEachChunk(new RunnableVal4() { + public void run(final Integer rcx, final Integer rcz, Integer offset, Integer size) { + pool.submit(new Runnable() { @Override - public void run(final Integer rcx, final Integer rcz, Integer offset, Integer size) { + public void run() { int cx = cbx + rcx; int cz = cbz + rcz; if (filter.appliesChunk(cx, cz)) { @@ -102,6 +106,7 @@ public class MCAQueue extends NMSMappedFaweQueue> 1; if ((index & 1) == 0) { - return data[index] & 15; + return data[indexShift] & 15; } else { - return data[index] >> 4 & 15; + return (data[indexShift] >> 4) & 15; } } } diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/CavesGen.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/CavesGen.java new file mode 100644 index 00000000..b6047d00 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/CavesGen.java @@ -0,0 +1,256 @@ +package com.boydti.fawe.jnbt.anvil.generator; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.object.PseudoRandom; +import com.boydti.fawe.util.MathMan; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.BlockID; +import com.sk89q.worldedit.extent.Extent; + +public class CavesGen extends GenBase { + + private boolean evenCaveDistribution = false; + private int caveFrequency = 40; + private int caveRarity = 7; + private int caveMinAltitude = 8; + private int caveMaxAltitude = 127; + private int caveSystemFrequency = 1; + private int individualCaveRarity = 25; + private int caveSystemPocketChance = 0; + private int caveSystemPocketMinSize = 0; + private int caveSystemPocketMaxSize = 3; + + public CavesGen(int caveSize) { + super(caveSize); + } + + public CavesGen(int caveSize, int caveFrequency, int caveRarity, int caveMinAltitude, int caveMaxAltitude, int caveSystemFrequency, int individualCaveRarity, int caveSystemPocketChance, int caveSystemPocketMinSize, int caveSystemPocketMaxSize) { + super(caveSize); + this.caveFrequency = caveFrequency; + this.caveRarity = caveRarity; + this.caveMinAltitude = caveMinAltitude; + this.caveMaxAltitude = caveMaxAltitude; + this.caveSystemFrequency = caveSystemFrequency; + this.individualCaveRarity = individualCaveRarity; + this.caveSystemPocketChance = caveSystemPocketChance; + this.caveSystemPocketMinSize = caveSystemPocketMinSize; + this.caveSystemPocketMaxSize = caveSystemPocketMaxSize; + } + + protected void generateLargeCaveNode(long seed, Vector2D pos, Extent chunk, double x, double y, double z) throws WorldEditException { + generateCaveNode(seed, pos, chunk, x, y, z, 1.0F + PseudoRandom.random.nextDouble() * 6.0F, 0.0F, 0.0F, -1, -1, 0.5D); + } + + protected void generateCaveNode(long seed, Vector2D chunkPos, Extent chunk, double x, double y, double z, double paramdouble1, double paramdouble2, double paramdouble3, int angle, int maxAngle, double paramDouble4) throws WorldEditException { + int bx = (chunkPos.getBlockX() << 4); + int bz = (chunkPos.getBlockZ() << 4); + double real_x = bx + 7; + double real_z = bz + 7; + + double f1 = 0.0F; + double f2 = 0.0F; + + PseudoRandom localRandom = new PseudoRandom(seed); + + if (maxAngle <= 0) { + int checkAreaSize = this.getCheckAreaSize() * 16 - 16; + maxAngle = checkAreaSize - localRandom.nextInt(checkAreaSize / 4); + } + boolean isLargeCave = false; + + if (angle == -1) { + angle = maxAngle / 2; + isLargeCave = true; + } + + int j = localRandom.nextInt(maxAngle / 2) + maxAngle / 4; + int k = localRandom.nextInt(6) == 0 ? 1 : 0; + + for (; angle < maxAngle; angle++) { + double d3 = 1.5D + MathMan.sinInexact(angle * 3.141593F / maxAngle) * paramdouble1 * 1.0F; + double d4 = d3 * paramDouble4; + + double f3 = MathMan.cosInexact(paramdouble3); + double f4 = MathMan.sinInexact(paramdouble3); + x += MathMan.cosInexact(paramdouble2) * f3; + y += f4; + z += MathMan.sinInexact(paramdouble2) * f3; + + if (k != 0) + paramdouble3 *= 0.92F; + else { + paramdouble3 *= 0.7F; + } + paramdouble3 += f2 * 0.1F; + paramdouble2 += f1 * 0.1F; + + f2 *= 0.9F; + f1 *= 0.75F; + f2 += (localRandom.nextDouble() - localRandom.nextDouble()) * localRandom.nextDouble() * 2.0F; + f1 += (localRandom.nextDouble() - localRandom.nextDouble()) * localRandom.nextDouble() * 4.0F; + + if ((!isLargeCave) && (angle == j) && (paramdouble1 > 1.0F) && (maxAngle > 0)) { + generateCaveNode(localRandom.nextLong(), chunkPos, chunk, x, y, z, localRandom.nextDouble() * 0.5F + 0.5F, paramdouble2 - 1.570796F, paramdouble3 / 3.0F, angle, maxAngle, 1.0D); + generateCaveNode(localRandom.nextLong(), chunkPos, chunk, x, y, z, localRandom.nextDouble() * 0.5F + 0.5F, paramdouble2 + 1.570796F, paramdouble3 / 3.0F, angle, maxAngle, 1.0D); + return; + } + if ((!isLargeCave) && (localRandom.nextInt(4) == 0)) { + continue; + } + + // Check if distance to working point (x and z) too larger than working radius (maybe ??) + double d5 = x - real_x; + double d6 = z - real_z; + double d7 = maxAngle - angle; + double d8 = paramdouble1 + 2.0F + 16.0F; + if (d5 * d5 + d6 * d6 - d7 * d7 > d8 * d8) { + return; + } + + //Boundaries check. + if ((x < real_x - 16.0D - d3 * 2.0D) || (z < real_z - 16.0D - d3 * 2.0D) || (x > real_x + 16.0D + d3 * 2.0D) || (z > real_z + 16.0D + d3 * 2.0D)) + continue; + + + int m = (int) (x - d3) - bx - 1; + int n = (int) (x + d3) - bx + 1; + + int i1 = (int) (y - d4) - 1; + int i2 = (int) (y + d4) + 1; + + int i3 = (int) (z - d3) - bz - 1; + int i4 = (int) (z + d3) - bz + 1; + + if (m < 0) + m = 0; + if (n > 16) + n = 16; + + if (i1 < 1) + i1 = 1; + if (i2 > 256 - 8) { + i2 = 256 - 8; + } + if (i3 < 0) + i3 = 0; + if (i4 > 16) + i4 = 16; + + // Search for water + boolean waterFound = false; + for (int local_x = m; (!waterFound) && (local_x < n); local_x++) { + for (int local_z = i3; (!waterFound) && (local_z < i4); local_z++) { + for (int local_y = i2 + 1; (!waterFound) && (local_y >= i1 - 1); local_y--) { + if (local_y >= 0 && local_y < 255) { + BaseBlock material = chunk.getLazyBlock(bx + local_x, local_y, bz + local_z); + if (material.getId() == 8 || material.getId() == 9) { + waterFound = true; + } + if ((local_y != i1 - 1) && (local_x != m) && (local_x != n - 1) && (local_z != i3) && (local_z != i4 - 1)) + local_y = i1; + } + } + } + } + if (waterFound) { + continue; + } + + // Generate cave + for (int local_x = m; local_x < n; local_x++) { + double d9 = (local_x + bx + 0.5D - x) / d3; + for (int local_z = i3; local_z < i4; local_z++) { + double d10 = (local_z + bz + 0.5D - z) / d3; + boolean grassFound = false; + if (d9 * d9 + d10 * d10 < 1.0D) { + for (int local_y = i2; local_y > i1; local_y--) { + double d11 = ((local_y - 1) + 0.5D - y) / d4; + if ((d11 > -0.7D) && (d9 * d9 + d11 * d11 + d10 * d10 < 1.0D)) { + BaseBlock material = chunk.getLazyBlock(bx + local_x, local_y, bz + local_z); + BaseBlock materialAbove = chunk.getLazyBlock(bx + local_x, local_y + 1, bz + local_z); + if (material.getId() == BlockID.GRASS || material.getId() == BlockID.MYCELIUM) { + grassFound = true; + } + if (this.isSuitableBlock(material, materialAbove)) { + if (local_y - 1 < 10) { + chunk.setBlock(bx + local_x, local_y, bz + local_z, FaweCache.getBlock(BlockID.LAVA, 0)); + } else { + chunk.setBlock(bx + local_x, local_y, bz + local_z, FaweCache.getBlock(0, 0)); + + // If grass was just deleted, try to + // move it down + if (grassFound) { + BaseBlock block = chunk.getLazyBlock(bx + local_x, local_y - 1, bz + local_z); + if (block.getId() == BlockID.DIRT) { + chunk.setBlock(bx + local_x, local_y - 1, bz + local_z, FaweCache.getBlock(BlockID.STONE, 0)); + } + } + } + } + } + } + } + } + } + if (isLargeCave) + break; + } + } + + protected boolean isSuitableBlock(BaseBlock material, BaseBlock materialAbove) { + switch (material.getId()) { + case 0: + case 8: + case 9: + case 10: + case 11: + case 7: + return false; + default: + return true; + } + } + + @Override + public void generateChunk(Vector2D adjacentChunk, Vector2D originChunk, Extent chunk) throws WorldEditException { + PseudoRandom random = getRandom(); + int i = random.nextInt(random.nextInt(random.nextInt(this.caveFrequency) + 1) + 1); + if (this.evenCaveDistribution) + i = this.caveFrequency; + if (random.nextInt(100) >= this.caveRarity) + i = 0; + + for (int j = 0; j < i; j++) { + double x = (adjacentChunk.getBlockX() << 4) + random.nextInt(16); + + double y; + + if (this.evenCaveDistribution) + y = random.nextInt(this.caveMinAltitude, this.caveMaxAltitude); + else + y = random.nextInt(random.nextInt(this.caveMaxAltitude - this.caveMinAltitude + 1) + 1) + this.caveMinAltitude; + + double z = (adjacentChunk.getBlockZ() << 4) + random.nextInt(16); + + int count = this.caveSystemFrequency; + boolean largeCaveSpawned = false; + if (random.nextInt(100) <= this.individualCaveRarity) { + generateLargeCaveNode(random.nextLong(), originChunk, chunk, x, y, z); + largeCaveSpawned = true; + } + + if ((largeCaveSpawned) || (random.nextInt(100) <= this.caveSystemPocketChance - 1)) { + count += random.nextInt(this.caveSystemPocketMinSize, this.caveSystemPocketMaxSize); + } + while (count > 0) { + count--; + double f1 = random.nextDouble() * 3.141593F * 2.0F; + double f2 = (random.nextDouble() - 0.5F) * 2.0F / 8.0F; + double f3 = random.nextDouble() * 2.0F + random.nextDouble(); + generateCaveNode(random.nextLong(), originChunk, chunk, x, y, z, f3, f1, f2, 0, 0, 1.0D); + } + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/GenBase.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/GenBase.java new file mode 100644 index 00000000..ca46d4fe --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/GenBase.java @@ -0,0 +1,49 @@ +package com.boydti.fawe.jnbt.anvil.generator; + +import com.boydti.fawe.object.PseudoRandom; +import com.sk89q.worldedit.MutableBlockVector2D; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.Extent; + +public abstract class GenBase { + + private final int checkAreaSize; + private final PseudoRandom random; + private final long seed; + private final long worldSeed1, worldSeed2; + private MutableBlockVector2D mutable = new MutableBlockVector2D(); + + public GenBase(int area) { + this.random = new PseudoRandom(); + this.checkAreaSize = area; + this.seed = PseudoRandom.random.nextLong(); + this.worldSeed1 = PseudoRandom.random.nextLong(); + this.worldSeed2 = PseudoRandom.random.nextLong(); + } + + public int getCheckAreaSize() { + return checkAreaSize; + } + + public PseudoRandom getRandom() { + return random; + } + + public void generate(Vector2D chunkPos, Extent chunk) throws WorldEditException { + int i = this.checkAreaSize; + int chunkX = chunkPos.getBlockX(); + int chunkZ = chunkPos.getBlockZ(); + + for (int x = chunkX - i; x <= chunkX + i; x++) { + mutable.mutX(x); + for (int z = chunkZ - i; z <= chunkZ + i; z++) { + mutable.mutZ(z); + this.random.setSeed(worldSeed1 * x ^ worldSeed2 * z ^ seed); + generateChunk(mutable, chunkPos, chunk); + } + } + } + + public abstract void generateChunk(Vector2D adjacentChunk, Vector2D originChunk, Extent chunk) throws WorldEditException; +} diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/OreGen.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/OreGen.java new file mode 100644 index 00000000..8e28f6bd --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/OreGen.java @@ -0,0 +1,118 @@ +package com.boydti.fawe.jnbt.anvil.generator; + +import com.boydti.fawe.object.PseudoRandom; +import com.boydti.fawe.util.MathMan; +import com.sk89q.worldedit.MutableBlockVector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.pattern.Pattern; + +public class OreGen extends Resource { + private final int maxSize; + private final double maxSizeO8; + private final double maxSizeO16; + private final double sizeInverse; + private final int minY; + private final int maxY; + private final Pattern pattern; + private final Extent extent; + private final Mask mask; + private MutableBlockVector mutable = new MutableBlockVector(); + + private double ONE_2 = 1 / 2F; + private double ONE_8 = 1 / 8F; + private double ONE_16 = 1 / 16F; + + public int laced =0; + + public OreGen(Extent extent, Mask mask, Pattern pattern, int size, int minY, int maxY) { + this.maxSize = size; + this.maxSizeO8 = size * ONE_8; + this.maxSizeO16 = size * ONE_16; + this.sizeInverse = 1.0 / size; + this.minY = minY; + this.maxY = maxY; + this.mask = mask; + this.pattern = pattern; + this.extent = extent; + } + + @Override + public boolean spawn(PseudoRandom rand, int x, int z) throws WorldEditException { + int y = rand.nextInt(minY, maxY); + if (!mask.test(mutable.setComponents(x, y, z))) { + return false; + } + double f = rand.nextDouble() * Math.PI; + + int x8 = x + 8; + int z8 = z + 8; + double so8 = maxSizeO8; + double so16 = maxSizeO16; + double sf = MathMan.sinInexact(f) * so8; + double cf = MathMan.cosInexact(f) * so8; + double d1 = x8 + sf; + double d2 = x8 - sf; + double d3 = z8 + cf; + double d4 = z8 - cf; + + double d5 = y + rand.nextInt(3) - 2; + double d6 = y + rand.nextInt(3) - 2; + + double xd = (d2 - d1); + double yd = (d6 - d5); + double zd = (d4 - d3); + + double iFactor = 0; + for (int i = 0; i < maxSize; i++, iFactor += sizeInverse) { + double d7 = d1 + xd * iFactor; + double d8 = d5 + yd * iFactor; + double d9 = d3 + zd * iFactor; + + double d10 = rand.nextDouble() * so16; + double sif = MathMan.sinInexact(Math.PI * iFactor); + double d11 = (sif + 1.0) * d10 + 1.0; + double d12 = (sif + 1.0) * d10 + 1.0; + + double d11o2 = d11 * ONE_2; + double d12o2 = d12 * ONE_2; + + int minX = MathMan.floorZero(d7 - d11o2); + int minY = Math.max(1, MathMan.floorZero(d8 - d12o2)); + int minZ = MathMan.floorZero(d9 - d11o2); + + int maxX = MathMan.floorZero(d7 + d11o2); + int maxY = Math.min(255, MathMan.floorZero(d8 + d12o2)); + int maxZ = MathMan.floorZero(d9 + d11o2); + + double id11o2 = 1.0 / (d11o2); + double id12o2 = 1.0 / (d12o2); + + for (int xx = minX; xx <= maxX; xx++) { + double dx = (xx + 0.5D - d7) * id11o2; + double dx2 = dx * dx; + if (dx2 < 1) { + mutable.mutX(xx); + for (int yy = minY; yy <= maxY; yy++) { + double dy = (yy + 0.5D - d8) * id12o2; + double dxy2 = dx2 + dy * dy; + if (dxy2 < 1) { + mutable.mutY(yy); + for (int zz = minZ; zz <= maxZ; zz++) { + mutable.mutZ(zz); + double dz = (zz + 0.5D - d9) * id11o2; + double dxyz2 = dxy2 + dz * dz; + if ((dxyz2 < 1)) { + if (mask.test(mutable)) + extent.setBlock(xx, yy, zz, pattern.apply(mutable)); + } + } + } + } + } + } + } + return true; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/Resource.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/Resource.java new file mode 100644 index 00000000..8bdc53d2 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/Resource.java @@ -0,0 +1,11 @@ +package com.boydti.fawe.jnbt.anvil.generator; + +import com.boydti.fawe.object.PseudoRandom; +import com.sk89q.worldedit.WorldEditException; + +public abstract class Resource { + public Resource() { + } + + public abstract boolean spawn(PseudoRandom random, int x, int z) throws WorldEditException; +} diff --git a/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/SchemGen.java b/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/SchemGen.java new file mode 100644 index 00000000..6142c1f5 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/jnbt/anvil/generator/SchemGen.java @@ -0,0 +1,56 @@ +package com.boydti.fawe.jnbt.anvil.generator; + +import com.boydti.fawe.object.PseudoRandom; +import com.boydti.fawe.object.schematic.Schematic; +import com.sk89q.worldedit.MutableBlockVector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.math.transform.AffineTransform; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.world.registry.WorldData; + +public class SchemGen extends Resource { + + private final Extent extent; + private final WorldData worldData; + private final ClipboardHolder[] clipboards; + private final boolean randomRotate; + private final Mask mask; + + private MutableBlockVector mutable = new MutableBlockVector(); + + public SchemGen(Mask mask, Extent extent, WorldData worldData, ClipboardHolder[] clipboards, boolean randomRotate) { + this.mask = mask; + this.extent = extent; + this.worldData = worldData; + this.clipboards = clipboards; + this.randomRotate = randomRotate; + } + + @Override + public boolean spawn(PseudoRandom random, int x, int z) throws WorldEditException { + mutable.mutX(x); + mutable.mutZ(z); + int y = extent.getNearestSurfaceTerrainBlock(x, z, mutable.getBlockY(), 0, 255); + mutable.mutY(y); + if (!mask.test(mutable)) { + return false; + } + mutable.mutY(y + 1); + ClipboardHolder holder = clipboards[PseudoRandom.random.random(clipboards.length)]; + if (randomRotate) { + holder.setTransform(new AffineTransform().rotateY(PseudoRandom.random.random(4) * 90)); + } + Clipboard clipboard = holder.getClipboard(); + Schematic schematic = new Schematic(clipboard); + if (holder.getTransform().isIdentity()) { + schematic.paste(extent, mutable, false); + } else { + schematic.paste(extent, worldData, mutable, false, holder.getTransform()); + } + mutable.mutY(y); + return true; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/FaweChunk.java b/core/src/main/java/com/boydti/fawe/object/FaweChunk.java index b21fe936..44f73238 100644 --- a/core/src/main/java/com/boydti/fawe/object/FaweChunk.java +++ b/core/src/main/java/com/boydti/fawe/object/FaweChunk.java @@ -295,6 +295,14 @@ public abstract class FaweChunk implements Callable { public abstract void setBiome(final int x, final int z, final byte biome); + public void setBiome(final byte biome) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + setBiome(x, z, biome); + } + } + } + /** * Spend time now so that the chunk can be more efficiently dispatched later
* - Modifications after this call will be ignored diff --git a/core/src/main/java/com/boydti/fawe/object/HistoryExtent.java b/core/src/main/java/com/boydti/fawe/object/HistoryExtent.java index 8cd547ad..a0dbf462 100644 --- a/core/src/main/java/com/boydti/fawe/object/HistoryExtent.java +++ b/core/src/main/java/com/boydti/fawe/object/HistoryExtent.java @@ -146,7 +146,7 @@ public class HistoryExtent extends AbstractDelegateExtent { } } - private class TrackedEntity implements Entity { + public class TrackedEntity implements Entity { private final Entity entity; private TrackedEntity(final Entity entity) { diff --git a/core/src/main/java/com/boydti/fawe/object/PseudoRandom.java b/core/src/main/java/com/boydti/fawe/object/PseudoRandom.java index 1b1bdee3..0d5cc13b 100644 --- a/core/src/main/java/com/boydti/fawe/object/PseudoRandom.java +++ b/core/src/main/java/com/boydti/fawe/object/PseudoRandom.java @@ -1,7 +1,5 @@ package com.boydti.fawe.object; -import java.util.Random; - public class PseudoRandom { public static PseudoRandom random = new PseudoRandom(); @@ -10,13 +8,16 @@ public class PseudoRandom { public PseudoRandom() { this.state = System.nanoTime(); - new Random().nextDouble(); } public PseudoRandom(final long state) { this.state = state; } + public void setSeed(long state) { + this.state = state; + } + public long nextLong() { final long a = this.state; this.state = this.xorShift64(a); @@ -45,4 +46,8 @@ public class PseudoRandom { public int nextInt(int i) { return random(i); } + + public int nextInt(int start, int end) { + return nextInt(end - start + 1) + start; + } } diff --git a/core/src/main/java/com/boydti/fawe/object/RegionWrapper.java b/core/src/main/java/com/boydti/fawe/object/RegionWrapper.java index 164c8010..1c178dd2 100644 --- a/core/src/main/java/com/boydti/fawe/object/RegionWrapper.java +++ b/core/src/main/java/com/boydti/fawe/object/RegionWrapper.java @@ -5,6 +5,8 @@ import com.sk89q.worldedit.Vector; public class RegionWrapper { public int minX; public int maxX; + public int minY; + public int maxY; public int minZ; public int maxZ; @@ -13,10 +15,16 @@ public class RegionWrapper { } public RegionWrapper(final int minX, final int maxX, final int minZ, final int maxZ) { + this(minX, maxX, 0, 255, minZ, maxZ); + } + + public RegionWrapper(final int minX, final int maxX, final int minY, final int maxY, final int minZ, final int maxZ) { this.maxX = maxX; this.minX = minX; this.maxZ = maxZ; this.minZ = minZ; + this.minY = minY; + this.maxY = Math.min(255, maxY); } public RegionWrapper(final Vector pos1, final Vector pos2) { @@ -24,12 +32,35 @@ public class RegionWrapper { this.minZ = Math.min(pos1.getBlockZ(), pos2.getBlockZ()); this.maxX = Math.max(pos1.getBlockX(), pos2.getBlockX()); this.maxZ = Math.max(pos1.getBlockZ(), pos2.getBlockZ()); + this.minY = Math.min(pos1.getBlockY(), pos2.getBlockY()); + this.maxY = Math.max(pos1.getBlockY(), pos2.getBlockY()); } public RegionWrapper[] toArray() { return new RegionWrapper[]{this}; } + private int ly = Integer.MIN_VALUE; + private int lz = Integer.MIN_VALUE; + private boolean lr, lry, lrz; + + public boolean isIn(int x, int y, int z) { + if (z != lz) { + lz = z; + lrz = z >= this.minZ && z <= this.maxZ; + if (y != ly) { + ly = y; + lry = y >= this.minY && y <= this.maxY; + } + lr = lrz && lry; + } else if (y != ly) { + ly = y; + lry = y >= this.minY && y <= this.maxY; + lr = lrz && lry; + } + return lr && (x >= this.minX && x <= this.maxX); + } + public boolean isIn(final int x, final int z) { return ((x >= this.minX) && (x <= this.maxX) && (z >= this.minZ) && (z <= this.maxZ)); } @@ -91,7 +122,7 @@ public class RegionWrapper { } public boolean isGlobal() { - return minX == Integer.MIN_VALUE && minZ == Integer.MIN_VALUE && maxX == Integer.MAX_VALUE && maxZ == Integer.MAX_VALUE; + return minX == Integer.MIN_VALUE && minZ == Integer.MIN_VALUE && maxX == Integer.MAX_VALUE && maxZ == Integer.MAX_VALUE && minY <= 0 && maxY >= 255; } public boolean contains(RegionWrapper current) { diff --git a/core/src/main/java/com/boydti/fawe/object/brush/LayerBrush.java b/core/src/main/java/com/boydti/fawe/object/brush/LayerBrush.java new file mode 100644 index 00000000..804e3dc7 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/brush/LayerBrush.java @@ -0,0 +1,85 @@ +package com.boydti.fawe.object.brush; + +import com.boydti.fawe.object.FaweQueue; +import com.boydti.fawe.object.collection.BlockVectorSet; +import com.boydti.fawe.object.mask.AdjacentAnyMask; +import com.boydti.fawe.object.mask.RadiusMask; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.MutableBlockVector; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.command.tool.brush.Brush; +import com.sk89q.worldedit.function.RegionFunction; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.mask.SolidBlockMask; +import com.sk89q.worldedit.function.operation.Operations; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.function.visitor.BreadthFirstSearch; +import com.sk89q.worldedit.function.visitor.RecursiveVisitor; +import java.util.Arrays; + +public class LayerBrush implements Brush { + + private final BaseBlock[] layers; + private RecursiveVisitor visitor; + private MutableBlockVector mutable = new MutableBlockVector(); + + public LayerBrush(BaseBlock[] layers) { + this.layers = layers; + } + + @Override + public void build(EditSession editSession, Vector position, Pattern ignore, double size) throws MaxChangedBlocksException { + final FaweQueue queue = editSession.getQueue(); + final AdjacentAnyMask adjacent = new AdjacentAnyMask(editSession, Arrays.asList(new BaseBlock(0))); + final SolidBlockMask solid = new SolidBlockMask(editSession); + final RadiusMask radius = new RadiusMask(0, (int) size); + visitor = new RecursiveVisitor(vector -> solid.test(vector) && radius.test(vector) && adjacent.test(vector), function -> true); + visitor.visit(position); + visitor.setDirections(Arrays.asList(BreadthFirstSearch.DIAGONAL_DIRECTIONS)); + Operations.completeBlindly(visitor); + BlockVectorSet visited = visitor.getVisited(); + BaseBlock firstPattern = layers[0]; + visitor = new RecursiveVisitor(new Mask() { + @Override + public boolean test(Vector pos) { + int depth = visitor.getDepth() + 1; + if (depth > 1) { + boolean found = false; + int previous = layers[depth - 1].getCombined(); + int previous2 = layers[depth - 2].getCombined(); + for (Vector dir : BreadthFirstSearch.DEFAULT_DIRECTIONS) { + mutable.setComponents(pos.getBlockX() + dir.getBlockX(), pos.getBlockY() + dir.getBlockY(), pos.getBlockZ() + dir.getBlockZ()); + if (visitor.isVisited(mutable) && queue.getCachedCombinedId4Data(mutable.getBlockX(), mutable.getBlockY(), mutable.getBlockZ()) == previous) { + mutable.setComponents(pos.getBlockX() + dir.getBlockX() * 2, pos.getBlockY() + dir.getBlockY() * 2, pos.getBlockZ() + dir.getBlockZ() * 2); + if (visitor.isVisited(mutable) && queue.getCachedCombinedId4Data(mutable.getBlockX(), mutable.getBlockY(), mutable.getBlockZ()) == previous2) { + found = true; + break; + } else { + return false; + } + } + } + if (!found) { + return false; + } + } + return !adjacent.test(pos); + } + }, new RegionFunction() { + @Override + public boolean apply(Vector pos) throws WorldEditException { + int depth = visitor.getDepth(); + BaseBlock currentPattern = layers[depth]; + return editSession.setBlock(pos, currentPattern); + } + }, layers.length - 1, editSession); + for (Vector pos : visited) { + visitor.visit(pos); + } + Operations.completeBlindly(visitor); + visitor = null; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/brush/PopulateSchem.java b/core/src/main/java/com/boydti/fawe/object/brush/PopulateSchem.java new file mode 100644 index 00000000..76b1f198 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/brush/PopulateSchem.java @@ -0,0 +1,39 @@ +package com.boydti.fawe.object.brush; + +import com.boydti.fawe.jnbt.anvil.generator.SchemGen; +import com.boydti.fawe.util.MaskTraverser; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.MaxChangedBlocksException; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.command.tool.brush.Brush; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.session.ClipboardHolder; + +public class PopulateSchem implements Brush { + private final Mask mask; + private final boolean randomRotate; + private final ClipboardHolder[] clipboards; + private final int rarity; + + public PopulateSchem(Mask mask, ClipboardHolder[] clipboards, int rarity, boolean randomRotate) { + this.mask = mask; + this.clipboards = clipboards; + this.rarity = rarity; + this.randomRotate = randomRotate; + } + + @Override + public void build(EditSession editSession, Vector position, Pattern pattern, double size) throws MaxChangedBlocksException { + new MaskTraverser(mask).reset(editSession); + SchemGen gen = new SchemGen(mask, editSession, editSession.getWorldData(), clipboards, randomRotate); + CuboidRegion cuboid = new CuboidRegion(position.subtract(size, size, size), position.add(size, size, size)); + try { + editSession.addSchems(cuboid, mask, editSession.getWorldData(), clipboards, rarity, randomRotate); + } catch (WorldEditException e) { + throw new RuntimeException(e); + } + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/brush/SplatterBrush.java b/core/src/main/java/com/boydti/fawe/object/brush/SplatterBrush.java index 0b517d16..a2659e9e 100644 --- a/core/src/main/java/com/boydti/fawe/object/brush/SplatterBrush.java +++ b/core/src/main/java/com/boydti/fawe/object/brush/SplatterBrush.java @@ -50,7 +50,7 @@ public class SplatterBrush extends ScatterBrush { @Override public boolean test(Vector vector) { double dist = vector.distanceSq(position); - if (!placed.contains(vector) && (PseudoRandom.random.random(5) < 2) && solid.test(vector) && adjacent.test(vector)) { + if (dist < size2 && !placed.contains(vector) && (PseudoRandom.random.random(5) < 2) && solid.test(vector) && adjacent.test(vector)) { placed.add(vector); return true; } diff --git a/core/src/main/java/com/boydti/fawe/object/brush/StencilBrush.java b/core/src/main/java/com/boydti/fawe/object/brush/StencilBrush.java index d0f825b9..0822ce46 100644 --- a/core/src/main/java/com/boydti/fawe/object/brush/StencilBrush.java +++ b/core/src/main/java/com/boydti/fawe/object/brush/StencilBrush.java @@ -7,7 +7,6 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.MaxChangedBlocksException; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.RegionFunction; import com.sk89q.worldedit.function.mask.Mask; @@ -40,9 +39,8 @@ public class StencilBrush extends HeightBrush { final HeightMap map = getHeightMap(); map.setSize(size); int cutoff = onlyWhite ? maxY : 0; - - final AdjacentAnyMask adjacent = new AdjacentAnyMask(editSession, Arrays.asList(new BaseBlock(0))); final SolidBlockMask solid = new SolidBlockMask(editSession); + final AdjacentAnyMask adjacent = new AdjacentAnyMask(editSession, solid.getInverseBlocks()); RegionMask region = new RegionMask(new CuboidRegion(position.subtract(size, size, size), position.add(size, size, size))); RecursiveVisitor visitor = new RecursiveVisitor(new Mask() { @Override diff --git a/core/src/main/java/com/boydti/fawe/object/collection/LocalBlockVectorSet.java b/core/src/main/java/com/boydti/fawe/object/collection/LocalBlockVectorSet.java index 186d959e..b0e7a4ec 100644 --- a/core/src/main/java/com/boydti/fawe/object/collection/LocalBlockVectorSet.java +++ b/core/src/main/java/com/boydti/fawe/object/collection/LocalBlockVectorSet.java @@ -312,6 +312,8 @@ public class LocalBlockVectorSet implements Set { @Override public void clear() { + offsetZ = Integer.MAX_VALUE; + offsetX = Integer.MAX_VALUE; set.clear(); } } diff --git a/core/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java b/core/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java index 0024fb1b..bd6687ba 100644 --- a/core/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java +++ b/core/src/main/java/com/boydti/fawe/object/extent/FastWorldEditExtent.java @@ -10,7 +10,6 @@ import com.sk89q.jnbt.DoubleTag; import com.sk89q.jnbt.ListTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; -import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.Vector2D; import com.sk89q.worldedit.WorldEditException; @@ -105,9 +104,6 @@ public class FastWorldEditExtent extends AbstractDelegateExtent implements HasFa @Override public BaseBlock getLazyBlock(int x, int y, int z) { - if (y > maxY || y < 0) { - return EditSession.nullBlock; - } int combinedId4Data = queue.getCombinedId4Data(x, y, z, 0); int id = FaweCache.getId(combinedId4Data); if (!FaweCache.hasNBT(id)) { @@ -149,9 +145,6 @@ public class FastWorldEditExtent extends AbstractDelegateExtent implements HasFa @Override public boolean setBlock(int x, int y, int z, final BaseBlock block) throws WorldEditException { - if (y > maxY || y < 0) { - return false; - } final short id = (short) block.getId(); switch (id) { case 63: diff --git a/core/src/main/java/com/boydti/fawe/object/extent/FaweRegionExtent.java b/core/src/main/java/com/boydti/fawe/object/extent/FaweRegionExtent.java index 8452d0a3..2f31e8f3 100644 --- a/core/src/main/java/com/boydti/fawe/object/extent/FaweRegionExtent.java +++ b/core/src/main/java/com/boydti/fawe/object/extent/FaweRegionExtent.java @@ -1,21 +1,200 @@ package com.boydti.fawe.object.extent; +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FaweLimit; import com.boydti.fawe.object.RegionWrapper; +import com.boydti.fawe.util.WEManager; +import com.sk89q.worldedit.EditSession; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.extent.AbstractDelegateExtent; import com.sk89q.worldedit.extent.Extent; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.biome.BaseBiome; import java.util.Collection; +import javax.annotation.Nullable; public abstract class FaweRegionExtent extends AbstractDelegateExtent { + private final FaweLimit limit; + /** * Create a new instance. * * @param extent the extent */ - public FaweRegionExtent(Extent extent) { + public FaweRegionExtent(Extent extent, FaweLimit limit) { super(extent); + this.limit = limit; } public abstract boolean contains(int x, int y, int z); + public abstract boolean contains(int x, int z); + public abstract Collection getRegions(); + + public boolean isGlobal() { + for (RegionWrapper region : getRegions()) { + if (region.isGlobal()) { + return true; + } + } + return false; + } + + public final boolean contains(Vector p) { + return contains(p.getBlockX(), p.getBlockY(), p.getBlockZ()); + } + + public final boolean contains(Vector2D p) { + return contains(p.getBlockX(), p.getBlockZ()); + } + + @Override + public boolean setBlock(Vector location, BaseBlock block) throws WorldEditException { + if (!contains(location)) { + if (!limit.MAX_FAILS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + } + return false; + } + return super.setBlock(location, block); + } + + @Override + public boolean setBlock(int x, int y, int z, BaseBlock block) throws WorldEditException { + if (!contains(x, y, z)) { + if (!limit.MAX_FAILS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + } + return false; + } + return super.setBlock(x, y, z, block); + } + + @Override + public boolean setBiome(Vector2D position, BaseBiome biome) { + if (!contains(position)) { + if (!limit.MAX_FAILS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + } + return false; + } + return super.setBiome(position, biome); + } + + @Override + public BaseBiome getBiome(Vector2D position) { + if (!contains(position)) { + if (!limit.MAX_FAILS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + } + return EditSession.nullBiome; + } + return super.getBiome(position); + } + + @Override + public BaseBlock getBlock(Vector position) { + if (!contains(position)) { + if (!limit.MAX_FAILS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + } + return EditSession.nullBlock; + } + return super.getBlock(position); + } + + @Override + public BaseBlock getLazyBlock(Vector position) { + if (!contains(position)) { + if (!limit.MAX_FAILS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + } + return EditSession.nullBlock; + } + return super.getLazyBlock(position); + } + + @Override + public BaseBlock getLazyBlock(int x, int y, int z) { + if (!contains(x, y, z)) { + if (!limit.MAX_FAILS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + } + return EditSession.nullBlock; + } + return super.getLazyBlock(x, y, z); + } + + @Override + public int getBlockLight(int x, int y, int z) { + if (!contains(x, y, z)) { + if (!limit.MAX_FAILS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + } + return 0; + } + return super.getBlockLight(x, y, z); + } + + @Override + public int getBrightness(int x, int y, int z) { + if (!contains(x, y, z)) { + if (!limit.MAX_FAILS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + } + return 0; + } + return super.getBrightness(x, y, z); + } + + @Override + public int getLight(int x, int y, int z) { + if (!contains(x, y, z)) { + if (!limit.MAX_FAILS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + } + return 0; + } + return super.getLight(x, y, z); + } + + @Override + public int getOpacity(int x, int y, int z) { + if (!contains(x, y, z)) { + if (!limit.MAX_FAILS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + } + return 0; + } + return super.getOpacity(x, y, z); + } + + @Override + public int getSkyLight(int x, int y, int z) { + if (!contains(x, y, z)) { + if (!limit.MAX_FAILS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + } + return 0; + } + return super.getSkyLight(x, y, z); + } + + @Nullable + @Override + public Entity createEntity(Location location, BaseEntity entity) { + if (!contains(location.getBlockX(), location.getBlockY(), location.getBlockZ())) { + if (!limit.MAX_FAILS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + } + return null; + } + return super.createEntity(location, entity); + } } diff --git a/core/src/main/java/com/boydti/fawe/object/extent/HeightBoundExtent.java b/core/src/main/java/com/boydti/fawe/object/extent/HeightBoundExtent.java new file mode 100644 index 00000000..c31fe935 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/extent/HeightBoundExtent.java @@ -0,0 +1,39 @@ +package com.boydti.fawe.object.extent; + +import com.boydti.fawe.object.FaweLimit; +import com.boydti.fawe.object.RegionWrapper; +import com.sk89q.worldedit.extent.Extent; +import java.util.Arrays; +import java.util.Collection; + +public class HeightBoundExtent extends FaweRegionExtent { + + private final int min, max; + private int lastY; + private boolean lastResult; + + public HeightBoundExtent(Extent extent, FaweLimit limit, int min, int max) { + super(extent, limit); + this.min = min; + this.max = max; + } + + @Override + public boolean contains(int x, int z) { + return true; + } + + @Override + public boolean contains(int x, int y, int z) { + if (y == lastY) { + return lastResult; + } + lastY = y; + return lastResult = (y >= min && y <= max); + } + + @Override + public Collection getRegions() { + return Arrays.asList(new RegionWrapper(Integer.MIN_VALUE, Integer.MAX_VALUE, min, max, Integer.MIN_VALUE, Integer.MAX_VALUE)); + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/extent/MultiRegionExtent.java b/core/src/main/java/com/boydti/fawe/object/extent/MultiRegionExtent.java new file mode 100644 index 00000000..a7799305 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/extent/MultiRegionExtent.java @@ -0,0 +1,67 @@ +package com.boydti.fawe.object.extent; + +import com.boydti.fawe.object.FaweLimit; +import com.boydti.fawe.object.RegionWrapper; +import com.sk89q.worldedit.extent.Extent; +import java.util.Arrays; +import java.util.Collection; + +public class MultiRegionExtent extends FaweRegionExtent { + + private RegionWrapper region; + private final RegionWrapper[] regions; + private int index; + + /** + * Create a new instance. + * + * @param extent the extent + */ + public MultiRegionExtent(Extent extent, FaweLimit limit, RegionWrapper[]regions) { + super(extent, limit); + this.index = 0; + this.region = regions[0]; + this.regions = regions; + } + + @Override + public boolean contains(int x, int y, int z) { + if (region.isIn(x, y, z)) { + return true; + } + for (int i = 0; i < regions.length; i++) { + if (i != index) { + RegionWrapper current = regions[i]; + if (current.isIn(x, y, z)) { + region = current; + index = i; + return true; + } + } + } + return false; + } + + @Override + public boolean contains(int x, int z) { + if (region.isIn(x, z)) { + return true; + } + for (int i = 0; i < regions.length; i++) { + if (i != index) { + RegionWrapper current = regions[i]; + if (current.isIn(x, z)) { + region = current; + index = i; + return true; + } + } + } + return false; + } + + @Override + public Collection getRegions() { + return Arrays.asList(regions); + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/extent/NullExtent.java b/core/src/main/java/com/boydti/fawe/object/extent/NullExtent.java index 0ae705f0..31e11367 100644 --- a/core/src/main/java/com/boydti/fawe/object/extent/NullExtent.java +++ b/core/src/main/java/com/boydti/fawe/object/extent/NullExtent.java @@ -1,6 +1,7 @@ package com.boydti.fawe.object.extent; import com.boydti.fawe.config.BBC; +import com.boydti.fawe.object.FaweLimit; import com.boydti.fawe.object.RegionWrapper; import com.boydti.fawe.object.exception.FaweException; import com.sk89q.worldedit.Vector; @@ -29,7 +30,7 @@ public class NullExtent extends FaweRegionExtent { * @param extent the extent */ public NullExtent(Extent extent, BBC failReason) { - super(extent); + super(extent, FaweLimit.MAX); this.reason = failReason; } @@ -94,7 +95,14 @@ public class NullExtent extends FaweRegionExtent { } @Override - public boolean contains(int x, int y, int z) { return false; } + public boolean contains(int x, int z) { + throw new FaweException(reason); + } + + @Override + public boolean contains(int x, int y, int z) { + throw new FaweException(reason); + } @Override public Collection getRegions() { diff --git a/core/src/main/java/com/boydti/fawe/object/extent/ProcessedWEExtent.java b/core/src/main/java/com/boydti/fawe/object/extent/ProcessedWEExtent.java index 47fd8112..6eb52fd8 100644 --- a/core/src/main/java/com/boydti/fawe/object/extent/ProcessedWEExtent.java +++ b/core/src/main/java/com/boydti/fawe/object/extent/ProcessedWEExtent.java @@ -1,10 +1,9 @@ package com.boydti.fawe.object.extent; -import com.boydti.fawe.FaweCache; import com.boydti.fawe.config.BBC; import com.boydti.fawe.object.FaweLimit; -import com.boydti.fawe.object.RegionWrapper; import com.boydti.fawe.util.WEManager; +import com.sk89q.jnbt.CompoundTag; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.Vector2D; @@ -17,27 +16,18 @@ import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.biome.BaseBiome; -import java.util.Arrays; -import java.util.Collection; import java.util.List; -public class ProcessedWEExtent extends FaweRegionExtent { +public class ProcessedWEExtent extends AbstractDelegateExtent { private final FaweLimit limit; - private final RegionWrapper[] mask; private final AbstractDelegateExtent extent; - public ProcessedWEExtent(final Extent parent, final RegionWrapper[] mask, FaweLimit limit) { + public ProcessedWEExtent(final Extent parent, FaweLimit limit) { super(parent); - this.mask = mask; this.limit = limit; this.extent = (AbstractDelegateExtent) parent; } - @Override - public Collection getRegions() { - return Arrays.asList(mask); - } - public void setLimit(FaweLimit other) { this.limit.set(other); } @@ -47,16 +37,11 @@ public class ProcessedWEExtent extends FaweRegionExtent { if (entity == null) { return null; } - if (WEManager.IMP.maskContains(this.mask, location.getBlockX(), location.getBlockZ())) { - if (!limit.MAX_ENTITIES()) { - WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_ENTITIES); - return null; - } - return super.createEntity(location, entity); - } else if (!limit.MAX_FAILS()) { - WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + if (!limit.MAX_ENTITIES()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_ENTITIES); + return null; } - return null; + return super.createEntity(location, entity); } @Override @@ -74,23 +59,13 @@ public class ProcessedWEExtent extends FaweRegionExtent { return super.getEntities(region); } - int count = 0; - @Override public BaseBlock getLazyBlock(int x, int y, int z) { - count++; - if (WEManager.IMP.maskContains(this.mask, x, z)) { - if (!limit.MAX_CHECKS()) { - WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_CHECKS); - return EditSession.nullBlock; - } else { - return extent.getLazyBlock(x, y, z); - } - } else if (!limit.MAX_FAILS()) { - WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + if (!limit.MAX_CHECKS()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_CHECKS); return EditSession.nullBlock; } else { - return EditSession.nullBlock; + return extent.getLazyBlock(x, y, z); } } @@ -106,56 +81,33 @@ public class ProcessedWEExtent extends FaweRegionExtent { @Override public boolean setBlock(int x, int y, int z, BaseBlock block) throws WorldEditException { - if (block.hasNbtData() && FaweCache.hasNBT(block.getType())) { + CompoundTag nbt = block.getNbtData(); + if (nbt != null) { if (!limit.MAX_BLOCKSTATES()) { WEManager.IMP.cancelEdit(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_TILES); return false; - } else if (WEManager.IMP.maskContains(this.mask, x, z)) { + } else { if (!limit.MAX_CHANGES()) { WEManager.IMP.cancelEdit(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_CHANGES); return false; } return extent.setBlock(x, y, z, block); - } else if (!limit.MAX_FAILS()) { - WEManager.IMP.cancelEdit(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); - return false; - } else { - return false; } } - if (WEManager.IMP.maskContains(this.mask, x, z)) { - if (!limit.MAX_CHANGES()) { - WEManager.IMP.cancelEdit(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_CHANGES); - return false; - } else { - return extent.setBlock(x, y, z, block); - } - } else if (!limit.MAX_FAILS()) { - WEManager.IMP.cancelEdit(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); + if (!limit.MAX_CHANGES()) { + WEManager.IMP.cancelEdit(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_CHANGES); return false; } else { - return false; + return extent.setBlock(x, y, z, block); } } @Override public boolean setBiome(final Vector2D position, final BaseBiome biome) { - if (WEManager.IMP.maskContains(this.mask, position.getBlockX(), position.getBlockZ())) { - if (!limit.MAX_CHANGES()) { - WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_CHANGES); - return false; - } - return super.setBiome(position, biome); - } else if (!limit.MAX_FAILS()) { - WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_FAILS); - return false; - } else { + if (!limit.MAX_CHANGES()) { + WEManager.IMP.cancelEditSafe(this, BBC.WORLDEDIT_CANCEL_REASON_MAX_CHANGES); return false; } - } - - @Override - public boolean contains(int x, int y, int z) { - return WEManager.IMP.maskContains(this.mask, x, z); + return super.setBiome(position, biome); } } diff --git a/core/src/main/java/com/boydti/fawe/object/extent/SingleRegionExtent.java b/core/src/main/java/com/boydti/fawe/object/extent/SingleRegionExtent.java new file mode 100644 index 00000000..2072dd7d --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/extent/SingleRegionExtent.java @@ -0,0 +1,37 @@ +package com.boydti.fawe.object.extent; + +import com.boydti.fawe.object.FaweLimit; +import com.boydti.fawe.object.RegionWrapper; +import com.sk89q.worldedit.extent.Extent; +import java.util.Arrays; +import java.util.Collection; + +public class SingleRegionExtent extends FaweRegionExtent{ + + private final RegionWrapper region; + + /** + * Create a new instance. + * + * @param extent the extent + */ + public SingleRegionExtent(Extent extent, FaweLimit limit, RegionWrapper region) { + super(extent, limit); + this.region = region; + } + + @Override + public boolean contains(int x, int y, int z) { + return region.isIn(x, y, z); + } + + @Override + public boolean contains(int x, int z) { + return region.isIn(x, z); + } + + @Override + public Collection getRegions() { + return Arrays.asList(region); + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/io/BufferedRandomAccessFile.java b/core/src/main/java/com/boydti/fawe/object/io/BufferedRandomAccessFile.java index 31bd2204..64a114d4 100644 --- a/core/src/main/java/com/boydti/fawe/object/io/BufferedRandomAccessFile.java +++ b/core/src/main/java/com/boydti/fawe/object/io/BufferedRandomAccessFile.java @@ -141,6 +141,17 @@ public class BufferedRandomAccessFile extends RandomAccessFile this.init(size); } + public BufferedRandomAccessFile(File file, String mode, byte[] buf) throws FileNotFoundException + { + super(file, mode); + this.dirty_ = this.closed_ = false; + this.lo_ = this.curr_ = this.hi_ = 0; + this.buff_ = buf; + this.maxHi_ = (long) BuffSz_; + this.hitEOF_ = false; + this.diskPos_ = 0L; + } + private void init(int size) { this.dirty_ = this.closed_ = false; diff --git a/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java b/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java index 43c576d4..2abda806 100644 --- a/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java +++ b/core/src/main/java/com/boydti/fawe/object/mask/AngleMask.java @@ -1,9 +1,9 @@ package com.boydti.fawe.object.mask; -import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.MutableBlockVector; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.function.mask.Mask2D; import com.sk89q.worldedit.function.mask.SolidBlockMask; import javax.annotation.Nullable; @@ -15,16 +15,16 @@ public class AngleMask extends SolidBlockMask { private final double max; private final double min; - private final EditSession extent; + private final Extent extent; private MutableBlockVector mutable = new MutableBlockVector(); private int maxY; - public AngleMask(EditSession editSession, double min, double max) { - super(editSession); - this.extent = editSession; + public AngleMask(Extent extent, double min, double max) { + super(extent); + this.extent = extent; this.min = min; this.max = max; - this.maxY = extent.getMaxY(); + this.maxY = extent.getMaximumPoint().getBlockY(); } @Override @@ -32,11 +32,11 @@ public class AngleMask extends SolidBlockMask { int x = vector.getBlockX(); int y = vector.getBlockY(); int z = vector.getBlockZ(); - BaseBlock block = extent.getBlock(x, y, z); + BaseBlock block = extent.getLazyBlock(x, y, z); if (!test(block.getId(), block.getData())) { return false; } - block = extent.getBlock(x, y + 1, z); + block = extent.getLazyBlock(x, y + 1, z); if (test(block.getId(), block.getData())) { return false; } diff --git a/core/src/main/java/com/boydti/fawe/object/mask/SkyLightMask.java b/core/src/main/java/com/boydti/fawe/object/mask/SkyLightMask.java index 172bc791..871172fb 100644 --- a/core/src/main/java/com/boydti/fawe/object/mask/SkyLightMask.java +++ b/core/src/main/java/com/boydti/fawe/object/mask/SkyLightMask.java @@ -32,4 +32,4 @@ public class SkyLightMask implements Mask { public Mask2D toMask2D() { return null; } -} +} \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/object/pattern/BiomePattern.java b/core/src/main/java/com/boydti/fawe/object/pattern/BiomePattern.java index 26e4086e..2db6e115 100644 --- a/core/src/main/java/com/boydti/fawe/object/pattern/BiomePattern.java +++ b/core/src/main/java/com/boydti/fawe/object/pattern/BiomePattern.java @@ -43,7 +43,7 @@ public class BiomePattern extends ExistingPattern { } @Override - public synchronized Throwable fillInStackTrace() { + public Throwable fillInStackTrace() { return this; } } diff --git a/core/src/main/java/com/boydti/fawe/object/player/DefaultFawePlayer.java b/core/src/main/java/com/boydti/fawe/object/player/DefaultFawePlayer.java deleted file mode 100644 index b8c75714..00000000 --- a/core/src/main/java/com/boydti/fawe/object/player/DefaultFawePlayer.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.boydti.fawe.object.player; - -public class DefaultFawePlayer { -} diff --git a/core/src/main/java/com/boydti/fawe/object/regions/PolyhedralRegion.java b/core/src/main/java/com/boydti/fawe/object/regions/PolyhedralRegion.java new file mode 100644 index 00000000..3e966b86 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/regions/PolyhedralRegion.java @@ -0,0 +1,337 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.boydti.fawe.object.regions; + +import com.sk89q.worldedit.LocalWorld; +import com.sk89q.worldedit.MutableBlockVector; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.regions.AbstractRegion; +import com.sk89q.worldedit.regions.RegionOperationException; +import com.sk89q.worldedit.regions.polyhedron.Edge; +import com.sk89q.worldedit.world.World; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; + + +import static com.google.common.base.Preconditions.checkNotNull; + +public class PolyhedralRegion extends AbstractRegion { + + /** + * Vertices that are contained in the convex hull. + */ + private final Set vertices = new LinkedHashSet(); + + /** + * Triangles that form the convex hull. + */ + private final List triangles = new ArrayList(); + + /** + * Vertices that are coplanar to the first 3 vertices. + */ + private final Set vertexBacklog = new LinkedHashSet(); + + /** + * Minimum point of the axis-aligned bounding box. + */ + private Vector minimumPoint; + + /** + * Maximum point of the axis-aligned bounding box. + */ + private Vector maximumPoint; + + /** + * Accumulator for the barycenter of the polyhedron. Divide by vertices.size() to get the actual center. + */ + private Vector centerAccum = Vector.ZERO; + + /** + * The last triangle that caused a {@link #contains(Vector)} to classify a point as "outside". Used for optimization. + */ + private Triangle lastTriangle; + + /** + * Constructs an empty mesh, containing no vertices or triangles. + * + * @param world the world + */ + public PolyhedralRegion(@Nullable World world) { + super(world); + } + + /** + * @deprecated cast {@code world} to {@link World} + */ + @Deprecated + public PolyhedralRegion(LocalWorld world) { + super(world); + } + + /** + * Constructs an independent copy of the given region. + * + * @param region the region to copy + */ + public PolyhedralRegion(PolyhedralRegion region) { + this(region.world); + vertices.addAll(region.vertices); + triangles.addAll(region.triangles); + vertexBacklog.addAll(region.vertexBacklog); + + minimumPoint = region.minimumPoint; + maximumPoint = region.maximumPoint; + centerAccum = region.centerAccum; + lastTriangle = region.lastTriangle; + } + + /** + * Clears the region, removing all vertices and triangles. + */ + public void clear() { + vertices.clear(); + triangles.clear(); + vertexBacklog.clear(); + + minimumPoint = null; + maximumPoint = null; + centerAccum = Vector.ZERO; + lastTriangle = null; + } + + + + + /** + * Add a vertex to the region. + * + * @param vertex the vertex + * @return true, if something changed. + */ + public boolean addVertex(Vector vertex) { + checkNotNull(vertex); + + lastTriangle = null; // Probably not necessary + + if (vertices.contains(vertex)) { + return false; + } + + if (vertices.size() == 3) { + if (vertexBacklog.contains(vertex)) { + return false; + } + + if (containsRaw(vertex)) { + return vertexBacklog.add(vertex); + } + } + + vertices.add(vertex); + + centerAccum = centerAccum.add(vertex); + + if (minimumPoint == null) { + minimumPoint = maximumPoint = vertex; + } else { + minimumPoint = new MutableBlockVector(Vector.getMinimum(minimumPoint, vertex)); + maximumPoint = new MutableBlockVector(Vector.getMaximum(maximumPoint, vertex)); + } + + int size = vertices.size(); + switch (size) { + case 0: + case 1: + case 2: + // Incomplete, can't make a mesh yet + return true; + + case 3: + // Generate minimal mesh to start from + final Vector[] v = vertices.toArray(new Vector[vertices.size()]); + + triangles.add((new Triangle(v[0], v[size - 2], v[size - 1]))); + triangles.add((new Triangle(v[0], v[size - 1], v[size - 2]))); + return true; + } + final Set borderEdges = new LinkedHashSet(); + for (Iterator it = triangles.iterator(); it.hasNext(); ) { + final Triangle triangle = it.next(); + + // If the triangle can't be seen, it's not relevant + if (!triangle.above(vertex)) { + continue; + } + + // Remove the triangle from the mesh + it.remove(); + + // ...and remember its edges + for (int i = 0; i < 3; ++i) { + final Edge edge = triangle.getEdge(i); + if (borderEdges.remove(edge)) { + continue; + } + + borderEdges.add(edge); + } + } + + // Add triangles between the remembered edges and the new vertex. + for (Edge edge : borderEdges) { + com.sk89q.worldedit.regions.polyhedron.Triangle triangle = edge.createTriangle(vertex); + Triangle fTria = new Triangle(triangle.getVertex(0), triangle.getVertex(1), triangle.getVertex(2)); + triangles.add(fTria); + } + + if (!vertexBacklog.isEmpty()) { + // Remove the new vertex + vertices.remove(vertex); + + // Clone, clear and work through the backlog + final List vertexBacklog2 = new ArrayList(vertexBacklog); + vertexBacklog.clear(); + for (Vector vertex2 : vertexBacklog2) { + addVertex(vertex2); + } + + // Re-add the new vertex after the backlog. + vertices.add(vertex); + } + return true; + } + + public boolean isDefined() { + return !triangles.isEmpty(); + } + + @Override + public Vector getMinimumPoint() { + return minimumPoint; + } + + @Override + public Vector getMaximumPoint() { + return maximumPoint; + } + + @Override + public Vector getCenter() { + return centerAccum.divide(vertices.size()); + } + + @Override + public void expand(Vector... changes) throws RegionOperationException { + } + + @Override + public void contract(Vector... changes) throws RegionOperationException { + } + + @Override + public void shift(Vector change) throws RegionOperationException { + shiftCollection(vertices, change); + shiftCollection(vertexBacklog, change); + + for (int i = 0; i < triangles.size(); ++i) { + final Triangle triangle = triangles.get(i); + + final Vector v0 = change.add(triangle.getVertex(0)); + final Vector v1 = change.add(triangle.getVertex(1)); + final Vector v2 = change.add(triangle.getVertex(2)); + + triangles.set(i, new Triangle(v0, v1, v2)); + } + + minimumPoint = change.add(minimumPoint); + maximumPoint = change.add(maximumPoint); + centerAccum = change.multiply(vertices.size()).add(centerAccum); + lastTriangle = null; + } + + private static void shiftCollection(Collection collection, Vector change) { + final List tmp = new ArrayList(collection); + collection.clear(); + for (Vector vertex : tmp) { + collection.add(change.add(vertex)); + } + } + + @Override + public boolean contains(Vector position) { + if (!isDefined()) { + return false; + } + final int x = position.getBlockX(); + final int y = position.getBlockY(); + final int z = position.getBlockZ(); + final Vector min = getMinimumPoint(); + final Vector max = getMaximumPoint(); + if (x < min.getBlockX()) return false; + if (x > max.getBlockX()) return false; + if (z < min.getBlockZ()) return false; + if (z > max.getBlockZ()) return false; + if (y < min.getBlockY()) return false; + if (y > max.getBlockY()) return false; + return containsRaw(position); + } + + private boolean containsRaw(Vector pt) { + if (lastTriangle != null && lastTriangle.contains(pt)) { + return true; + } + for (Triangle triangle : triangles) { + if (lastTriangle == triangle) { + continue; + } + if (triangle.contains(pt)) { + lastTriangle = triangle; + return true; + } + } + return false; + } + + public Collection getVertices() { + if (vertexBacklog.isEmpty()) { + return vertices; + } + + final List ret = new ArrayList(vertices); + ret.addAll(vertexBacklog); + + return ret; + } + + public Collection getTriangles() { + return triangles; + } + + @Override + public AbstractRegion clone() { + return new PolyhedralRegion(this); + } +} \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/object/regions/Triangle.java b/core/src/main/java/com/boydti/fawe/object/regions/Triangle.java new file mode 100644 index 00000000..5f8b3323 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/regions/Triangle.java @@ -0,0 +1,246 @@ +package com.boydti.fawe.object.regions; + +import com.boydti.fawe.util.MathMan; +import com.boydti.fawe.util.StringMan; +import com.google.common.base.Preconditions; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.regions.polyhedron.Edge; + +public class Triangle { + + public static double RADIUS = 0.5; + + private final double[][] verts = new double[3][3]; + private final double[] center = new double[3]; + private final double[] radius = new double[3]; + private final double[] v0 = new double[3]; + private final double[] v1 = new double[3]; + private final double[] v2 = new double[3]; + private final double[] normal = new double[3]; + private final double[] e0 = new double[3]; + private final double[] e1 = new double[3]; + private final double[] e2 = new double[3]; + private final double[] vmin = new double[3]; + private final double[] vmax = new double[3]; + + private final Vector normalVec; + private final double b; + + public Triangle(Vector pos1, Vector pos2, Vector pos3) { + verts[0] = new double[]{pos1.getBlockX(), pos1.getBlockY(), pos1.getBlockZ()}; + verts[1] = new double[]{pos2.getBlockX(), pos2.getBlockY(), pos2.getBlockZ()}; + verts[2] = new double[]{pos3.getBlockX(), pos3.getBlockY(), pos3.getBlockZ()}; + radius[0] = RADIUS; + radius[1] = RADIUS; + radius[2] = RADIUS; + this.normalVec = pos2.subtract(pos1).cross(pos3.subtract(pos1)).normalize(); + this.b = Math.max(Math.max(this.normalVec.dot(pos1), this.normalVec.dot(pos2)), this.normalVec.dot(pos3)); + } + + public boolean above(Vector pt) { + Preconditions.checkNotNull(pt); + return this.normalVec.dot(pt) > this.b; + } + + public Edge getEdge(int index) { + if (index == this.verts.length - 1) { + return new Edge(new Vector(this.verts[index]), new Vector(this.verts[0])); + } else { + return new Edge(new Vector(this.verts[index]), new Vector(this.verts[index + 1])); + } + } + + @Override + public String toString() { + return StringMan.getString(verts); + } + + public Vector getVertex(int index) { + return new Vector(verts[index]); + } + + public boolean contains(Vector pos) { + center[0] = pos.getBlockX() + RADIUS; + center[1] = pos.getBlockY() + RADIUS; + center[2] = pos.getBlockZ() + RADIUS; + return overlaps(center, radius, verts); + } + + private void sub(double[] dest, double[] v1, double[] v2) { + dest[0] = v1[0] - v2[0]; + dest[1] = v1[1] - v2[1]; + dest[2] = v1[2] - v2[2]; + } + + private void cross(double[] dest, double[] v1, double[] v2) { + dest[0] = v1[1] * v2[2] - v1[2] * v2[1]; + dest[1] = v1[2] * v2[0] - v1[0] * v2[2]; + dest[2] = v1[0] * v2[1] - v1[1] * v2[0]; + } + + private double dot(double[] v1, double[] v2) { + return (v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]); + } + + + private boolean axisTestX01(double a, double b, double fa, double fb) { + double p0 = a * v0[1] - b * v0[2]; + double p2 = a * v2[1] - b * v2[2]; + double min, max; + if (p0 < p2) { + min = p0; + max = p2; + } else { + min = p2; + max = p0; + } + double rad = fa * radius[1] + fb * radius[2]; + return !(min > rad || max < -rad); + } + + private boolean axisTestX2(double a, double b, double fa, double fb) { + double p0 = a * v0[1] - b * v0[2]; + double p1 = a * v1[1] - b * v1[2]; + double min, max; + if (p0 < p1) { + min = p0; + max = p1; + } else { + min = p1; + max = p0; + } + double rad = fa * radius[1] + fb * radius[2]; + return !(min > rad || max < -rad); + } + + private boolean axisTestY02(double a, double b, double fa, double fb) { + double p0 = -a * v0[0] + b * v0[2]; + double p2 = -a * v2[0] + b * v2[2]; + double min, max; + if (p0 < p2) { + min = p0; + max = p2; + } else { + min = p2; + max = p0; + } + double rad = fa * radius[0] + fb * radius[2]; + return !(min > rad || max < -rad); + } + + private boolean axisTestY1(double a, double b, double fa, double fb) { + double p0 = -a * v0[0] + b * v0[2]; + double p1 = -a * v1[0] + b * v1[2]; + double min, max; + if (p0 < p1) { + min = p0; + max = p1; + } else { + min = p1; + max = p0; + } + double rad = fa * radius[0] + fb * radius[2]; + return !(min > rad || max < -rad); + } + + private boolean axisTestZ12(double a, double b, double fa, double fb) { + double p1 = a * v1[0] - b * v1[1]; + double p2 = a * v2[0] - b * v2[1]; + double min, max; + if (p2 < p1) { + min = p2; + max = p1; + } else { + min = p1; + max = p2; + } + double rad = fa * radius[0] + fb * radius[1]; + return !(min > rad || max < -rad); + } + + private boolean axisTestZ0(double a, double b, double fa, double fb) { + double p0 = a * v0[0] - b * v0[1]; + double p1 = a * v1[0] - b * v1[1]; + double min, max; + if (p0 < p1) { + min = p0; + max = p1; + } else { + min = p1; + max = p0; + } + double rad = fa * radius[0] + fb * radius[1]; + return !(min > rad || max < -rad); + } + + + private boolean overlaps(double boxcenter[], double boxhalfsize[], double triverts[][]) { + double min, max, p0, p1, p2, rad, fex, fey, fez; + sub(v0, triverts[0], boxcenter); + sub(v1, triverts[1], boxcenter); + sub(v2, triverts[2], boxcenter); + sub(e0, v1, v0); /* tri edge 0 */ + sub(e1, v2, v1); /* tri edge 1 */ + sub(e2, v0, v2); /* tri edge 2 */ + + fex = Math.abs(e0[0]); + fey = Math.abs(e0[1]); + fez = Math.abs(e0[2]); + + if (!axisTestX01(e0[2], e0[1], fez, fey)) return false; + if (!axisTestY02(e0[2], e0[0], fez, fex)) return false; + if (!axisTestZ12(e0[1], e0[0], fey, fex)) return false; + + fex = Math.abs(e1[0]); + fey = Math.abs(e1[1]); + fez = Math.abs(e1[2]); + + if (!axisTestX01(e1[2], e1[1], fez, fey)) return false; + if (!axisTestY02(e1[2], e1[0], fez, fex)) return false; + if (!axisTestZ0(e1[1], e1[0], fey, fex)) return false; + + + fex = Math.abs(e2[0]); + fey = Math.abs(e2[1]); + fez = Math.abs(e2[2]); + + if (!axisTestX2(e2[2], e2[1], fez, fey)) return false; + if (!axisTestY1(e2[2], e2[0], fez, fex)) return false; + if (!axisTestZ12(e2[1], e2[0], fey, fex)) return false; + + max = MathMan.max(v0[0], v1[0], v2[0]); + min = MathMan.min(v0[0], v1[0], v2[0]); + + if (min > boxhalfsize[0] || max < -boxhalfsize[0]) return false; + + max = MathMan.max(v0[1], v1[1], v2[1]); + min = MathMan.min(v0[1], v1[1], v2[1]); + + if (min > boxhalfsize[1] || max < -boxhalfsize[1]) return false; + + max = MathMan.max(v0[2], v1[2], v2[2]); + min = MathMan.min(v0[2], v1[2], v2[2]); + + if (min > boxhalfsize[2] || max < -boxhalfsize[2]) return false; + + cross(normal, e0, e1); + + return (planeBoxOverlap(normal, v0, boxhalfsize)); + } + + private boolean planeBoxOverlap(double normal[], double vert[], double maxbox[]) { + for (int q = 0; q <= 2; q++) { + double v = vert[q]; + if (normal[q] > 0.0f) { + vmin[q] = -maxbox[q] - v; + vmax[q] = maxbox[q] - v; + } else { + vmin[q] = maxbox[q] - v; + vmax[q] = -maxbox[q] - v; + } + } + if (dot(normal, vmin) > 0.0f) return false; + if (dot(normal, vmax) >= 0.0f) return true; + return false; + } +} diff --git a/core/src/main/java/com/boydti/fawe/object/regions/selector/PolyhedralRegionSelector.java b/core/src/main/java/com/boydti/fawe/object/regions/selector/PolyhedralRegionSelector.java new file mode 100644 index 00000000..771a569d --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/object/regions/selector/PolyhedralRegionSelector.java @@ -0,0 +1,237 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit team and contributors + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.boydti.fawe.object.regions.selector; + +import com.boydti.fawe.object.regions.PolyhedralRegion; +import com.boydti.fawe.object.regions.Triangle; +import com.google.common.base.Optional; +import com.sk89q.worldedit.BlockVector; +import com.sk89q.worldedit.IncompleteRegionException; +import com.sk89q.worldedit.LocalSession; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.internal.cui.CUIRegion; +import com.sk89q.worldedit.internal.cui.SelectionPointEvent; +import com.sk89q.worldedit.internal.cui.SelectionPolygonEvent; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.regions.RegionSelector; +import com.sk89q.worldedit.regions.selector.limit.SelectorLimits; +import com.sk89q.worldedit.world.World; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; + + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Creates a {@code PolyhedralRegion} from a user's selections. + */ +public class PolyhedralRegionSelector implements RegionSelector, CUIRegion { + + private final transient PolyhedralRegion region; + private transient BlockVector pos1; + + /** + * Create a new selector with a {@code null} world. + */ + public PolyhedralRegionSelector() { + this((World) null); + } + + /** + * Create a new selector. + * + * @param world the world, which may be {@code null} + */ + public PolyhedralRegionSelector(@Nullable World world) { + region = new PolyhedralRegion(world); + } + + @Nullable + @Override + public World getWorld() { + return region.getWorld(); + } + + @Override + public void setWorld(@Nullable World world) { + region.setWorld(world); + } + + @Override + public boolean selectPrimary(Vector position, SelectorLimits limits) { + checkNotNull(position); + clear(); + pos1 = position.toBlockVector(); + return region.addVertex(position); + } + + @Override + public boolean selectSecondary(Vector position, SelectorLimits limits) { + checkNotNull(position); + + Optional vertexLimit = limits.getPolyhedronVertexLimit(); + + if (vertexLimit.isPresent() && region.getVertices().size() > vertexLimit.get()) { + return false; + } + + return region.addVertex(position); + } + + @Override + public BlockVector getPrimaryPosition() throws IncompleteRegionException { + return pos1; + } + + @Override + public Region getRegion() throws IncompleteRegionException { + if (!region.isDefined()) { + throw new IncompleteRegionException(); + } + + return region; + } + + @Override + public Region getIncompleteRegion() { + return region; + } + + @Override + public boolean isDefined() { + return region.isDefined(); + } + + @Override + public int getArea() { + return region.getArea(); + } + + @Override + public void learnChanges() { + pos1 = region.getVertices().iterator().next().toBlockVector(); + } + + @Override + public void clear() { + region.clear(); + } + + @Override + public String getTypeName() { + return "Polyhedron"; + } + + @Override + public List getInformationLines() { + List ret = new ArrayList(); + + ret.add("Vertices: "+region.getVertices().size()); + ret.add("Triangles: "+region.getTriangles().size()); + + return ret; + } + + + @Override + public void explainPrimarySelection(Actor player, LocalSession session, Vector pos) { + checkNotNull(player); + checkNotNull(session); + checkNotNull(pos); + + session.describeCUI(player); + + player.print("Started new selection with vertex "+pos+"."); + } + + @Override + public void explainSecondarySelection(Actor player, LocalSession session, Vector pos) { + checkNotNull(player); + checkNotNull(session); + checkNotNull(pos); + + session.describeCUI(player); + + player.print("Added vertex " + pos + " to the selection."); + } + + @Override + public void explainRegionAdjust(Actor player, LocalSession session) { + checkNotNull(player); + checkNotNull(session); + session.describeCUI(player); + } + + @Override + public int getProtocolVersion() { + return 3; + } + + @Override + public String getTypeID() { + return "polyhedron"; + } + + @Override + public void describeCUI(LocalSession session, Actor player) { + checkNotNull(player); + checkNotNull(session); + + Collection vertices = region.getVertices(); + Collection triangles = region.getTriangles(); + + Map vertexIds = new HashMap(vertices.size()); + int lastVertexId = -1; + for (Vector vertex : vertices) { + vertexIds.put(vertex, ++lastVertexId); + session.dispatchCUIEvent(player, new SelectionPointEvent(lastVertexId, vertex, getArea())); + } + + for (Triangle triangle : triangles) { + final int[] v = new int[3]; + for (int i = 0; i < 3; ++i) { + v[i] = vertexIds.get(triangle.getVertex(i)); + } + session.dispatchCUIEvent(player, new SelectionPolygonEvent(v)); + } + } + + @Override + public String getLegacyTypeID() { + return "cuboid"; + } + + @Override + public void describeLegacyCUI(LocalSession session, Actor player) { + checkNotNull(player); + checkNotNull(session); + + if (isDefined()) { + session.dispatchCUIEvent(player, new SelectionPointEvent(0, region.getMinimumPoint(), getArea())); + session.dispatchCUIEvent(player, new SelectionPointEvent(1, region.getMaximumPoint(), getArea())); + } + } + +} diff --git a/core/src/main/java/com/boydti/fawe/regions/FaweMask.java b/core/src/main/java/com/boydti/fawe/regions/FaweMask.java index 7da727c9..b8cbd213 100644 --- a/core/src/main/java/com/boydti/fawe/regions/FaweMask.java +++ b/core/src/main/java/com/boydti/fawe/regions/FaweMask.java @@ -30,7 +30,7 @@ public class FaweMask { public HashSet getRegions() { final BlockVector lower = this.getLowerBound(); final BlockVector upper = this.getUpperBound(); - return new HashSet<>(Arrays.asList(new RegionWrapper(lower.getBlockX(), upper.getBlockX(), lower.getBlockZ(), upper.getBlockZ()))); + return new HashSet<>(Arrays.asList(new RegionWrapper(lower.getBlockX(), upper.getBlockX(), lower.getBlockY(), upper.getBlockY(), lower.getBlockZ(), upper.getBlockZ()))); } public String getName() { 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 new file mode 100644 index 00000000..3c1ab6d9 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/regions/general/plot/CreateFromImage.java @@ -0,0 +1,362 @@ +package com.boydti.fawe.regions.general.plot; + +import com.boydti.fawe.config.BBC; +import com.boydti.fawe.jnbt.anvil.HeightMapMCAGenerator; +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.RunnableVal; +import com.boydti.fawe.util.StringMan; +import com.boydti.fawe.util.TaskManager; +import com.intellectualcrafters.plot.PS; +import com.intellectualcrafters.plot.commands.Auto; +import com.intellectualcrafters.plot.commands.CommandCategory; +import com.intellectualcrafters.plot.commands.MainCommand; +import com.intellectualcrafters.plot.commands.RequiredType; +import com.intellectualcrafters.plot.config.C; +import com.intellectualcrafters.plot.config.Settings; +import com.intellectualcrafters.plot.object.Plot; +import com.intellectualcrafters.plot.object.PlotId; +import com.intellectualcrafters.plot.object.PlotPlayer; +import com.intellectualcrafters.plot.object.RunnableVal2; +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.WorldEdit; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.session.request.Request; +import com.sk89q.worldedit.world.World; +import com.sk89q.worldedit.world.biome.BaseBiome; +import com.sk89q.worldedit.world.biome.Biomes; +import com.sk89q.worldedit.world.registry.BiomeRegistry; +import com.sk89q.worldedit.world.registry.WorldData; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.List; +import javax.imageio.ImageIO; + +@CommandDeclaration( + command = "cfi", + permission = "plots.createfromimage", + aliases = {"createfromheightmap", "createfromimage", "cfhm"}, + category = CommandCategory.APPEARANCE, + requiredType = RequiredType.NONE, + description = "Generate a world from an image", + usage = "/plots cfi [url]" +) +public class CreateFromImage extends Command { + private final WorldEdit we; + + public CreateFromImage() { + super(MainCommand.getInstance(), true); + this.we = WorldEdit.getInstance(); + } + + @Override + public void execute(final PlotPlayer player, String[] args, RunnableVal3 confirm, RunnableVal2 whenDone) throws CommandException { + List argList = StringMan.split(StringMan.join(args, " "), ' '); + checkTrue(argList.size() >= 1, C.COMMAND_SYNTAX, getUsage()); + PlotAreaManager manager = PS.get().getPlotAreaManager(); + if (manager instanceof SinglePlotAreaManager) { + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + FawePlayer fp = FawePlayer.wrap(player.getName()); + if (argList.get(0).toLowerCase().startsWith("http")) { + final BufferedImage image; + try { + URL url = new URL(argList.get(0)); + if (!url.getHost().equals("i.imgur.com")) { + player.sendMessage("Images can only be loaded from i.imgur.com"); + return; + } + player.sendMessage(BBC.getPrefix() + "Loading image... (1)"); + image = ImageIO.read(url); + } catch (IOException e) { + player.sendMessage(e.getMessage()); + return; + } + fp.runAction(new Runnable() { + @Override + public void run() { + SinglePlotAreaManager sManager = (SinglePlotAreaManager) manager; + SinglePlotArea area = sManager.getArea(); + Plot plot = TaskManager.IMP.sync(new com.boydti.fawe.object.RunnableVal() { + @Override + public void run(Plot o) { + 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 + ""); + return; + } + if (area.getMeta("lastPlot") == null) { + area.setMeta("lastPlot", new PlotId(0, 0)); + } + PlotId lastId = (PlotId) area.getMeta("lastPlot"); + while (true) { + lastId = Auto.getNextPlotId(lastId, 1); + if (area.canClaim(player, lastId, lastId)) { + break; + } + } + area.setMeta("lastPlot", lastId); + this.value = area.getPlot(lastId); + this.value.setOwner(player.getUUID()); + } + }); + fp.sendMessage(BBC.getPrefix() + "Initializing components... (2)"); + fp.sendMessage(BBC.getPrefix() + "/2 cfi setbiome"); + fp.sendMessage(BBC.getPrefix() + "/2 cfi setoverlay"); + fp.sendMessage(BBC.getPrefix() + "/2 cfi setmain"); + fp.sendMessage(BBC.getPrefix() + "/2 cfi setfloor"); + fp.sendMessage(BBC.getPrefix() + "/2 cfi setcolumn"); + fp.sendMessage(BBC.getPrefix() + "/2 cfi addcaves"); + fp.sendMessage(BBC.getPrefix() + "/2 cfi addore[s]"); + fp.sendMessage(BBC.getPrefix() + "/2 cfi addschems"); + fp.sendMessage(BBC.getPrefix() + "/2 cfi done"); + fp.sendMessage(BBC.getPrefix() + "/2 cfi cancel"); + File folder = new File(PS.imp().getWorldContainer(), plot.getWorldName() + File.separator + "region"); + HeightMapMCAGenerator generator = new HeightMapMCAGenerator(image, folder); + player.setMeta("HMGenerator", generator); + player.setMeta("HMGeneratorPlot", plot); + } + }, true, false); + return; + } + fp.runAction(new Runnable() { + @Override + public void run() { + HeightMapMCAGenerator generator = player.getMeta("HMGenerator"); + Plot plot = player.getMeta("HMGeneratorPlot"); + if (generator == null) { + C.COMMAND_SYNTAX.send(player, getUsage()); + return; + } + if (argList.size() == 1) { + if (StringMan.isEqualIgnoreCaseToAny(argList.get(0), "setbiome", "setoverlay", "setmain", "setfloor", "setcolumn")) { + C.COMMAND_SYNTAX.send(player, "/2 cfi " + argList.get(0) + " [white-only]"); + C.COMMAND_SYNTAX.send(player, "/2 cfi " + argList.get(0) + " "); + return; + } else if (!StringMan.isEqualIgnoreCaseToAny(argList.get(0), "done", "cancel", "addcaves", "addore", "addores", "addschems")) { + C.COMMAND_SYNTAX.send(player, "/2 cfi "); + return; + } + } + ParserContext context = new ParserContext(); + context.setActor(fp.getPlayer()); + context.setWorld(fp.getWorld()); + context.setSession(fp.getSession()); + context.setExtent(generator); + Request.request().setExtent(generator); + try { + switch (argList.get(0).toLowerCase()) { + case "addschems": { + if (argList.size() != 5) { + C.COMMAND_SYNTAX.send(player, "/2 cfi " + argList.get(0) + " "); + return; + } + World world = fp.getWorld(); + WorldData wd = world.getWorldData(); + Mask mask = we.getMaskFactory().parseFromInput(argList.get(1), context); + ClipboardHolder[] clipboards = ClipboardFormat.SCHEMATIC.loadAllFromInput(fp.getPlayer(), wd, argList.get(2), true); + if (clipboards == null) { + return; + } + int rarity = Integer.parseInt(argList.get(3)); + boolean rotate = Boolean.parseBoolean(argList.get(4)); + generator.addSchems(mask, wd, clipboards, rarity, rotate); + player.sendMessage(BBC.getPrefix() + "Added schems, what's next?"); + return; + } + case "addores": + if (argList.size() != 2) { + C.COMMAND_SYNTAX.send(player, "/2 cfi " + argList.get(0) + " "); + return; + } + generator.addDefaultOres(we.getMaskFactory().parseFromInput(argList.get(1), context)); + player.sendMessage(BBC.getPrefix() + "Added ores, what's next?"); + return; + case "addore": { + if (argList.size() != 8) { + C.COMMAND_SYNTAX.send(player, "/2 cfi " + argList.get(0) + " "); + return; + } + // mask pattern size freq rarity miny maxy + Mask mask = we.getMaskFactory().parseFromInput(argList.get(1), context); + Pattern pattern = we.getPatternFactory().parseFromInput(argList.get(2), context); + int size = Integer.parseInt(argList.get(3)); + int frequency = Integer.parseInt(argList.get(4)); + int rarity = Integer.parseInt(argList.get(5)); + int min = Integer.parseInt(argList.get(6)); + int max = Integer.parseInt(argList.get(7)); + generator.addOre(mask, pattern, size, frequency, rarity, min, max); + player.sendMessage(BBC.getPrefix() + "Added ore, what's next?"); + return; + } + case "addcaves": { + generator.addCaves(); + player.sendMessage(BBC.getPrefix() + "Added caves, what's next?"); + return; + } + case "setbiome": { + int id; + if (argList.size() == 2) { + id = getBiome(argList.get(1), fp).getId(); + generator.setBiome(id); + } else { + id = getBiome(argList.get(2), fp).getId(); + BufferedImage img = getImgurImage(argList.get(1), fp); + if (img != null) { + boolean whiteOnly = argList.size() == 4 && Boolean.parseBoolean(argList.get(3)); + generator.setBiome(img, (byte) id, whiteOnly); + } else { + generator.setBiome(we.getMaskFactory().parseFromInput(argList.get(1), context), (byte) id); + } + } + player.sendMessage(BBC.getPrefix() + "Set biome, what's next?"); + return; + } + case "setoverlay": { + Pattern id; + if (argList.size() == 2) { + id = we.getPatternFactory().parseFromInput(argList.get(1), context); + generator.setOverlay(id); + } else { + id = we.getPatternFactory().parseFromInput(argList.get(2), context); + BufferedImage img = getImgurImage(argList.get(1), fp); + if (img != null) { + boolean whiteOnly = argList.size() == 4 && Boolean.parseBoolean(argList.get(3)); + generator.setOverlay(img, id, whiteOnly); + } else { + generator.setOverlay(we.getMaskFactory().parseFromInput(argList.get(1), context), id); + } + } + player.sendMessage(BBC.getPrefix() + "Set overlay, what's next?"); + return; + } + case "setmain": { + Pattern id; + if (argList.size() == 2) { + id = we.getPatternFactory().parseFromInput(argList.get(1), context); + generator.setMain(id); + } else { + id = we.getPatternFactory().parseFromInput(argList.get(2), context); + BufferedImage img = getImgurImage(argList.get(1), fp); + if (img != null) { + boolean whiteOnly = argList.size() == 4 && Boolean.parseBoolean(argList.get(3)); + generator.setMain(img, id, whiteOnly); + } else { + generator.setMain(we.getMaskFactory().parseFromInput(argList.get(1), context), id); + } + } + player.sendMessage(BBC.getPrefix() + "Set main, what's next?"); + return; + } + case "setfloor": { + Pattern id; + if (argList.size() == 2) { + id = we.getPatternFactory().parseFromInput(argList.get(1), context); + generator.setFloor(id); + } else { + id = we.getPatternFactory().parseFromInput(argList.get(2), context); + BufferedImage img = getImgurImage(argList.get(1), fp); + if (img != null) { + boolean whiteOnly = argList.size() == 4 && Boolean.parseBoolean(argList.get(3)); + generator.setFloor(img, id, whiteOnly); + } else { + generator.setFloor(we.getMaskFactory().parseFromInput(argList.get(1), context), id); + } + } + player.sendMessage(BBC.getPrefix() + "Set floor, what's next?"); + return; + } + case "setcolumn": { + Pattern id; + if (argList.size() == 2) { + id = we.getPatternFactory().parseFromInput(argList.get(1), context); + generator.setColumn(id); + } else { + id = we.getPatternFactory().parseFromInput(argList.get(2), context); + BufferedImage img = getImgurImage(argList.get(1), fp); + if (img != null) { + boolean whiteOnly = argList.size() == 4 && Boolean.parseBoolean(argList.get(3)); + generator.setColumn(img, id, whiteOnly); + } else { + generator.setColumn(we.getMaskFactory().parseFromInput(argList.get(1), context), id); + } + } + player.sendMessage(BBC.getPrefix() + "Set columns, what's next?"); + return; + } + case "done": + player.deleteMeta("HMGenerator"); + player.deleteMeta("HMGeneratorPlot"); + player.sendMessage("Generating... (4)"); + try { + generator.generate(); + } catch (IOException e) { + e.printStackTrace(); + player.sendMessage(e.getMessage() + " (see console)"); + return; + } + player.sendMessage("Done!"); + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + plot.teleportPlayer(player); + } + }); + return; + case "cancel": + player.deleteMeta("HMGenerator"); + player.deleteMeta("HMGeneratorPlot"); + player.sendMessage(BBC.getPrefix() + "Cancelled!"); + return; + default: + C.COMMAND_SYNTAX.send(player, getUsage()); + } + } catch (IOException e) { + player.sendMessage("Invalid url: " + e.getMessage()); + } catch (InputParseException e) { + player.sendMessage("Invalid mask " + e.getMessage()); + } catch (Throwable e) { + player.sendMessage("Error " + e.getMessage()); + } + } + }, true, false); + } + }); + } else { + player.sendMessage("Must have the `worlds` component enabled in the PlotSquared config.yml"); + } + } + + private BaseBiome getBiome(String arg, FawePlayer fp) { + World world = fp.getWorld(); + BiomeRegistry biomeRegistry = world.getWorldData().getBiomeRegistry(); + List knownBiomes = biomeRegistry.getBiomes(); + return Biomes.findBiomeByName(knownBiomes, arg, biomeRegistry); + } + + private BufferedImage getImgurImage(String arg, FawePlayer fp) throws IOException { + if (arg.startsWith("http")) { + URL url = new URL(arg); + if (!url.getHost().equalsIgnoreCase("i.imgur.com")) { + throw new IOException("Only i.imgur.com links are allowed!"); + } + fp.sendMessage(BBC.getPrefix() + "Downloading image... (3)"); + return ImageIO.read(url); + } + return null; + } +} \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/regions/general/plot/PlotSquaredFeature.java b/core/src/main/java/com/boydti/fawe/regions/general/plot/PlotSquaredFeature.java index 04b0a3c2..090b1492 100644 --- a/core/src/main/java/com/boydti/fawe/regions/general/plot/PlotSquaredFeature.java +++ b/core/src/main/java/com/boydti/fawe/regions/general/plot/PlotSquaredFeature.java @@ -33,6 +33,10 @@ public class PlotSquaredFeature extends FaweMaskManager { } if (MainCommand.getInstance().getCommand("generatebiome") == null) { new PlotSetBiome(); + new CreateFromImage(); + } + if (Settings.Enabled_Components.WORLDS) { + new ReplaceAll(); } } diff --git a/core/src/main/java/com/boydti/fawe/regions/general/plot/ReplaceAll.java b/core/src/main/java/com/boydti/fawe/regions/general/plot/ReplaceAll.java new file mode 100644 index 00000000..cec91c23 --- /dev/null +++ b/core/src/main/java/com/boydti/fawe/regions/general/plot/ReplaceAll.java @@ -0,0 +1,81 @@ +package com.boydti.fawe.regions.general.plot; + +import com.boydti.fawe.object.FawePlayer; +import com.boydti.fawe.object.RunnableVal; +import com.boydti.fawe.util.StringMan; +import com.boydti.fawe.util.TaskManager; +import com.boydti.fawe.wrappers.FakePlayer; +import com.intellectualcrafters.plot.commands.CommandCategory; +import com.intellectualcrafters.plot.commands.MainCommand; +import com.intellectualcrafters.plot.commands.RequiredType; +import com.intellectualcrafters.plot.config.C; +import com.intellectualcrafters.plot.object.Plot; +import com.intellectualcrafters.plot.object.PlotArea; +import com.intellectualcrafters.plot.object.PlotPlayer; +import com.intellectualcrafters.plot.object.RunnableVal2; +import com.intellectualcrafters.plot.object.RunnableVal3; +import com.intellectualcrafters.plot.object.worlds.SinglePlotArea; +import com.plotsquared.bukkit.util.BukkitSetupUtils; +import com.plotsquared.general.commands.Command; +import com.plotsquared.general.commands.CommandDeclaration; +import com.sk89q.worldedit.event.platform.CommandEvent; +import com.sk89q.worldedit.extension.platform.CommandManager; + +@CommandDeclaration( + command = "replaceall", + permission = "plots.replaceall", + category = CommandCategory.APPEARANCE, + requiredType = RequiredType.NONE, + description = "Replace all block in the plot", + usage = "/plots replaceall " +) +public class ReplaceAll extends Command { + public ReplaceAll() { + super(MainCommand.getInstance(), true); + } + + @Override + public void execute(final PlotPlayer player, String[] args, RunnableVal3 confirm, RunnableVal2 whenDone) throws CommandException { + checkTrue(args.length >= 1, C.COMMAND_SYNTAX, getUsage()); + final Plot plot = check(player.getCurrentPlot(), C.NOT_IN_PLOT); + checkTrue(plot.isOwner(player.getUUID()), C.NOW_OWNER); + checkTrue(plot.getRunning() == 0, C.WAIT_FOR_TIMER); + final PlotArea area = plot.getArea(); + if (area instanceof SinglePlotArea) { + plot.addRunning(); + FawePlayer fp = FawePlayer.wrap(player.getName()); + C.TASK_START.send(player); + TaskManager.IMP.async(new Runnable() { + @Override + public void run() { + fp.runAction(new Runnable() { + @Override + public void run() { + String worldName = plot.getWorldName(); + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + BukkitSetupUtils.manager.unload(worldName, true); + } + }); + FakePlayer actor = FakePlayer.getConsole(); + String cmd = "/replaceallpattern " + worldName + " " + StringMan.join(args, " "); + CommandEvent event = new CommandEvent(actor, cmd); + CommandManager.getInstance().handleCommandOnCurrentThread(event); + TaskManager.IMP.sync(new RunnableVal() { + @Override + public void run(Object value) { + plot.teleportPlayer(player); + } + }); + plot.removeRunning(); + } + }, true, false); + } + }); + } else { + player.sendMessage("Must have the `worlds` component enabled in the PlotSquared config.yml"); + return; + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/boydti/fawe/util/ImgurUtility.java b/core/src/main/java/com/boydti/fawe/util/ImgurUtility.java index c7d09bfa..b452280b 100644 --- a/core/src/main/java/com/boydti/fawe/util/ImgurUtility.java +++ b/core/src/main/java/com/boydti/fawe/util/ImgurUtility.java @@ -3,7 +3,7 @@ package com.boydti.fawe.util; import com.boydti.fawe.object.io.FastByteArrayOutputStream; import com.google.gson.Gson; import com.google.gson.JsonObject; -import java.io.BufferedInputStream; +import it.unimi.dsi.fastutil.io.FastBufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -24,7 +24,7 @@ public class ImgurUtility { } public static URL uploadImage(InputStream is) throws IOException { - is = new BufferedInputStream(is); + is = new FastBufferedInputStream(is); FastByteArrayOutputStream baos = new FastByteArrayOutputStream(Short.MAX_VALUE); int d; while ((d = is.read()) != -1) { diff --git a/core/src/main/java/com/boydti/fawe/util/MainUtil.java b/core/src/main/java/com/boydti/fawe/util/MainUtil.java index 469206f0..1b541352 100644 --- a/core/src/main/java/com/boydti/fawe/util/MainUtil.java +++ b/core/src/main/java/com/boydti/fawe/util/MainUtil.java @@ -24,8 +24,9 @@ import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.history.changeset.ChangeSet; import com.sk89q.worldedit.util.Location; -import java.io.BufferedInputStream; +import it.unimi.dsi.fastutil.io.FastBufferedInputStream; import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -58,7 +59,10 @@ import java.util.Map.Entry; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; import java.util.zip.GZIPInputStream; +import java.util.zip.Inflater; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import net.jpountz.lz4.LZ4Compressor; @@ -244,6 +248,43 @@ public class MainUtil { return LZ4Utils.maxCompressedLength(size); } + public static byte[] compress(byte[] bytes, byte[] buffer, Deflater deflate) throws IOException { + if (buffer == null) { + buffer = new byte[8192]; + } + if (deflate == null) { + deflate = new Deflater(1, false); + } else { + deflate.reset(); + } + deflate.setInput(bytes); + deflate.finish(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + while (!deflate.finished()) { + int n = deflate.deflate(buffer); + if (n != 0) baos.write(buffer, 0, n); + } + return baos.toByteArray(); + } + + public static byte[] decompress(byte[] bytes, byte[] buffer, Inflater inflater) throws IOException, DataFormatException { + if (buffer == null) { + buffer = new byte[8192]; + } + if (inflater == null) { + inflater = new Inflater(false); + } else { + inflater.reset(); + } + inflater.setInput(bytes); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + while (!inflater.finished()) { + int n = inflater.inflate(buffer); + if (n != 0) baos.write(buffer, 0, n); + } + return baos.toByteArray(); + } + public static byte[] compress(byte[] bytes, byte[] buffer, int level) { if (level == 0) { return bytes; @@ -302,14 +343,14 @@ public class MainUtil { public static FaweInputStream getCompressedIS(InputStream is, int buffer) throws IOException { int amount = (byte) is.read(); - is = new BufferedInputStream(is, buffer); + is = new FastBufferedInputStream(is, buffer); if (amount == 0) { return new FaweInputStream(is); } int amountAbs = Math.abs(amount); if (amountAbs > 6) { if (amount > 0) { - is = new BufferedInputStream(new GZIPInputStream(is, buffer)); + is = new FastBufferedInputStream(new GZIPInputStream(is, buffer)); } else { is = new ZstdInputStream(is); } @@ -836,8 +877,7 @@ public class MainUtil { if (file.isDirectory()) { deleteDirectory(files[i]); } else { - Fawe.debug("Deleting file: " + file); - file.delete(); + Fawe.debug("Deleting file: " + file + " | " + file.delete()); } } } diff --git a/core/src/main/java/com/boydti/fawe/util/MathMan.java b/core/src/main/java/com/boydti/fawe/util/MathMan.java index ff438efd..351528fa 100644 --- a/core/src/main/java/com/boydti/fawe/util/MathMan.java +++ b/core/src/main/java/com/boydti/fawe/util/MathMan.java @@ -21,6 +21,76 @@ public class MathMan { } } + private static float[] ANGLES = new float[65536]; + + public static float sinInexact(double paramFloat) { + return ANGLES[(int)(paramFloat * 10430.378F) & 0xFFFF]; + } + + public static float cosInexact(double paramFloat) { + return ANGLES[(int)(paramFloat * 10430.378F + 16384.0F) & 0xFFFF]; + } + + public static int floorZero(double d0) { + int i = (int)d0; + return d0 < (double) i ? i - 1 : i; + } + + public static double max(double... values) { + double max = Double.MIN_VALUE; + for (double d : values) { + if (d > max) { + max = d; + } + } + return max; + } + + public static int max(int... values) { + int max = Integer.MIN_VALUE; + for (int d : values) { + if (d > max) { + max = d; + } + } + return max; + } + + public static int min(int... values) { + int min = Integer.MAX_VALUE; + for (int d : values) { + if (d < min) { + min = d; + } + } + return min; + } + + public static double min(double... values) { + double min = Double.MAX_VALUE; + for (double d : values) { + if (d < min) { + min = d; + } + } + return min; + } + + public static int ceilZero(float floatNumber) { + int floor = (int)floatNumber; + return floatNumber > (float) floor ? floor + 1 : floor; + } + + public static int clamp(int check, int min, int max) { + return check > max?max:(check < min?min:check); + } + + static { + for(int i = 0; i < 65536; ++i) { + ANGLES[i] = (float)Math.sin((double)i * 3.141592653589793D * 2.0D / 65536.0D); + } + } + private final static int[] table = { 0, 16, 22, 27, 32, 35, 39, 42, 45, 48, 50, 53, 55, 57, 59, 61, 64, 65, 67, 69, 71, 73, 75, 76, 78, 80, 81, 83, diff --git a/core/src/main/java/com/boydti/fawe/util/WEManager.java b/core/src/main/java/com/boydti/fawe/util/WEManager.java index 3232792f..03889156 100644 --- a/core/src/main/java/com/boydti/fawe/util/WEManager.java +++ b/core/src/main/java/com/boydti/fawe/util/WEManager.java @@ -76,7 +76,7 @@ public class WEManager { */ public RegionWrapper[] getMask(final FawePlayer player, FaweMaskManager.MaskType type) { if (player.hasPermission("fawe.bypass") || !Settings.IMP.REGION_RESTRICTIONS) { - return new RegionWrapper[] {new RegionWrapper(Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE)}; + return new RegionWrapper[] {RegionWrapper.GLOBAL()}; } HashSet mask = new HashSet<>(); String world = player.getLocation().world; 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 0c0eb807..a94bd343 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 @@ -346,7 +346,7 @@ public class TaskBuilder extends Metadatable { public static final class TaskAbortException extends RuntimeException { @Override - public synchronized Throwable fillInStackTrace() { + public Throwable fillInStackTrace() { return this; } } diff --git a/core/src/main/java/com/sk89q/jnbt/NBTInputStream.java b/core/src/main/java/com/sk89q/jnbt/NBTInputStream.java index d5af94b7..7feb36fe 100644 --- a/core/src/main/java/com/sk89q/jnbt/NBTInputStream.java +++ b/core/src/main/java/com/sk89q/jnbt/NBTInputStream.java @@ -157,12 +157,12 @@ public final class NBTInputStream implements Closeable { int i = 0; if (is instanceof InputStream) { DataInputStream dis = (DataInputStream) is; - if (length > 720) { + if (length > 1024) { if (buf == null) { - buf = new byte[720]; + buf = new byte[1024]; } int left = length; - for (; left > 720; left -= 720) { + for (; left > 1024; left -= 1024) { dis.readFully(buf); for (byte b : buf) { byteReader.run(i++, b & 0xFF); @@ -173,12 +173,12 @@ public final class NBTInputStream implements Closeable { byteReader.run(i, dis.read()); } } else { - if (length > 720) { + if (length > 1024) { if (buf == null) { - buf = new byte[720]; + buf = new byte[1024]; } int left = length; - for (; left > 720; left -= 720) { + for (; left > 1024; left -= 1024) { is.readFully(buf); for (byte b : buf) { byteReader.run(i++, b & 0xFF); @@ -338,8 +338,17 @@ public final class NBTInputStream implements Closeable { case NBTConstants.TYPE_INT_ARRAY: length = is.readInt(); int[] data = new int[length]; - for (int i = 0; i < length; i++) { - data[i] = is.readInt(); + if (buf == null) { + buf = new byte[1024]; + } + int index = 0; + while (length > 0) { + int toRead = Math.min(length << 2, buf.length); + is.readFully(buf, 0, toRead); + for (int i = 0; i < toRead; i += 4, index++) { + data[index] = ((buf[i] << 24) + (buf[i + 1]<< 16) + (buf[i + 2] << 8) + (buf[i + 3] << 0)); + } + length -= toRead; } return (data); default: diff --git a/core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java b/core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java index 19e2dfad..7e5a5e67 100644 --- a/core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java +++ b/core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java @@ -80,6 +80,72 @@ public final class NBTOutputStream implements Closeable { writeTagPayload(tag); } + public void writeNamedTag(String name, String value) throws IOException { + checkNotNull(name); + checkNotNull(value); + int type = NBTConstants.TYPE_STRING; + writeNamedTagName(name, type); + byte[] bytes = value.getBytes(NBTConstants.CHARSET); + os.writeShort(bytes.length); + os.write(bytes); + } + + public void writeNamedTag(String name, int value) throws IOException { + checkNotNull(name); + int type = NBTConstants.TYPE_INT; + writeNamedTagName(name, type); + os.writeInt(value); + } + + public void writeNamedTag(String name, byte value) throws IOException { + checkNotNull(name); + int type = NBTConstants.TYPE_BYTE; + writeNamedTagName(name, type); + os.writeByte(value); + } + + public void writeNamedTag(String name, short value) throws IOException { + checkNotNull(name); + int type = NBTConstants.TYPE_SHORT; + writeNamedTagName(name, type); + os.writeShort(value); + } + + public void writeNamedTag(String name, long value) throws IOException { + checkNotNull(name); + int type = NBTConstants.TYPE_LONG; + writeNamedTagName(name, type); + os.writeLong(value); + } + + public void writeNamedTag(String name, byte[] bytes) throws IOException { + checkNotNull(name); + int type = NBTConstants.TYPE_BYTE_ARRAY; + writeNamedTagName(name, type); + os.writeInt(bytes.length); + os.write(bytes); + } + + public void writeNamedTag(String name, int[] data) throws IOException { + checkNotNull(name); + int type = NBTConstants.TYPE_INT_ARRAY; + writeNamedTagName(name, type); + os.writeInt(data.length); + for (int aData : data) { + os.writeInt(aData); + } + } + + public void writeNamedEmptyList(String name) throws IOException { + writeNamedEmptyList(name, NBTConstants.TYPE_COMPOUND); + } + + public void writeNamedEmptyList(String name, int type) throws IOException { + writeNamedTagName(name, NBTConstants.TYPE_LIST); + os.writeByte(type); + os.writeInt(0); + } + public void writeNamedTagName(String name, int type) throws IOException { byte[] nameBytes = name.getBytes(NBTConstants.CHARSET); os.writeByte(type); @@ -213,6 +279,9 @@ public final class NBTOutputStream implements Closeable { */ private void writeListTagPayload(ListTag tag) throws IOException { Class clazz = tag.getType(); + if (clazz == null) { + clazz = CompoundTag.class; + } List tags = tag.getValue(); int size = tags.size(); if (!tags.isEmpty()) { diff --git a/core/src/main/java/com/sk89q/worldedit/EditSession.java b/core/src/main/java/com/sk89q/worldedit/EditSession.java index 1be48b10..312a42f0 100644 --- a/core/src/main/java/com/sk89q/worldedit/EditSession.java +++ b/core/src/main/java/com/sk89q/worldedit/EditSession.java @@ -46,10 +46,13 @@ import com.boydti.fawe.object.collection.LocalBlockVectorSet; import com.boydti.fawe.object.exception.FaweException; import com.boydti.fawe.object.extent.FastWorldEditExtent; import com.boydti.fawe.object.extent.FaweRegionExtent; +import com.boydti.fawe.object.extent.HeightBoundExtent; import com.boydti.fawe.object.extent.LightingExtent; +import com.boydti.fawe.object.extent.MultiRegionExtent; import com.boydti.fawe.object.extent.NullExtent; import com.boydti.fawe.object.extent.ProcessedWEExtent; import com.boydti.fawe.object.extent.ResettableExtent; +import com.boydti.fawe.object.extent.SingleRegionExtent; import com.boydti.fawe.object.extent.SlowExtent; import com.boydti.fawe.object.extent.SourceMaskExtent; import com.boydti.fawe.object.mask.ResettableMask; @@ -319,15 +322,22 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting } } } + this.maxY = getWorld() == null ? 255 : world.getMaxY(); if (allowedRegions != null) { if (allowedRegions.length == 0) { this.extent = new NullExtent(this.extent, BBC.WORLDEDIT_CANCEL_REASON_NO_REGION); } else { - this.extent = new ProcessedWEExtent(this.extent, allowedRegions, this.limit); + this.extent = new ProcessedWEExtent(this.extent, this.limit); + if (allowedRegions.length == 1) { + this.extent = new SingleRegionExtent(this.extent, limit, allowedRegions[0]); + } else { + this.extent = new MultiRegionExtent(this.extent, limit, allowedRegions); + } } + } else { + this.extent = new HeightBoundExtent(this.extent, limit, 0, maxY); } this.extent = wrapExtent(this.extent, bus, event, Stage.BEFORE_HISTORY); - this.maxY = getWorld() == null ? 255 : world.getMaxY(); } /** @@ -981,84 +991,6 @@ public class EditSession extends AbstractWorld implements HasFaweQueue, Lighting return this.getHighestTerrainBlock(x, z, minY, maxY, false); } - public int getNearestSurfaceLayer(int x, int z, int y, int minY, int maxY) { - int clearanceAbove = maxY - y; - int clearanceBelow = y - minY; - int clearance = Math.min(clearanceAbove, clearanceBelow); - - BaseBlock block = getBlock(x, y, z); - boolean state = FaweCache.isLiquidOrGas(block.getId()); - int data1 = block.getData(); - int data2 = block.getData(); - int offset = state ? 0 : 1; - for (int d = 0; d <= clearance; d++) { - int y1 = y + d; - block = getLazyBlock(x, y1, z); - if (FaweCache.isLiquidOrGas(block.getId()) != state) { - return ((y1 - offset) << 3) - (7 - (state ? block.getData() : data1)); - } - data1 = block.getData(); - int y2 = y - d; - block = getLazyBlock(x, y2, z); - if (FaweCache.isLiquidOrGas(block.getId()) != state) { - return ((y2 + offset) << 3) - (7 - (state ? block.getData() : data2)); - } - data2 = block.getData(); - } - if (clearanceAbove != clearanceBelow) { - if (clearanceAbove < clearanceBelow) { - for (int layer = y - clearance - 1; layer >= minY; layer--) { - block = getLazyBlock(x, layer, z); - if (FaweCache.isLiquidOrGas(block.getId()) != state) { - return ((layer + offset) << 3) - (7 - (state ? block.getData() : data1)); - } - data1 = block.getData(); - } - } else { - for (int layer = y + clearance + 1; layer <= maxY; layer++) { - block = getLazyBlock(x, layer, z); - if (FaweCache.isLiquidOrGas(block.getId()) != state) { - return ((layer - offset) << 3) - (7 - (state ? block.getData() : data2)); - } - data2 = block.getData(); - } - } - } - return maxY << 4; - } - - public int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY) { - int clearanceAbove = maxY - y; - int clearanceBelow = y - minY; - int clearance = Math.min(clearanceAbove, clearanceBelow); - - BaseBlock block = getBlock(x, y, z); - boolean state = FaweCache.canPassThrough(block.getId(), block.getData()); - int offset = state ? 0 : 1; - for (int d = 0; d <= clearance; d++) { - int y1 = y + d; - block = getLazyBlock(x, y1, z); - if (FaweCache.canPassThrough(block.getId(), block.getData()) != state) return y1 - offset; - int y2 = y - d; - block = getLazyBlock(x, y2, z); - if (FaweCache.canPassThrough(block.getId(), block.getData()) != state) return y2 + offset; - } - if (clearanceAbove != clearanceBelow) { - if (clearanceAbove < clearanceBelow) { - for (int layer = y - clearance - 1; layer >= minY; layer--) { - block = getLazyBlock(x, layer, z); - if (FaweCache.canPassThrough(block.getId(), block.getData()) != state) return layer + offset; - } - } else { - for (int layer = y + clearance + 1; layer <= maxY; layer++) { - block = getLazyBlock(x, layer, z); - if (FaweCache.canPassThrough(block.getId(), block.getData()) != state) return layer - offset; - } - } - } - return maxY; - } - /** * Returns the highest solid 'terrain' block which can occur naturally. * diff --git a/core/src/main/java/com/sk89q/worldedit/Vector.java b/core/src/main/java/com/sk89q/worldedit/Vector.java index 5938c130..1e60b5c2 100644 --- a/core/src/main/java/com/sk89q/worldedit/Vector.java +++ b/core/src/main/java/com/sk89q/worldedit/Vector.java @@ -88,6 +88,12 @@ public class Vector implements Comparable { this.mutZ(other.getZ()); } + public Vector(double[] arr) { + this.mutX(arr[0]); + this.mutY(arr[1]); + this.mutZ(arr[2]); + } + /** * Construct a new instance with X, Y, and Z coordinates set to 0. * diff --git a/core/src/main/java/com/sk89q/worldedit/blocks/BaseBlock.java b/core/src/main/java/com/sk89q/worldedit/blocks/BaseBlock.java index d0da313a..81a1cdaf 100644 --- a/core/src/main/java/com/sk89q/worldedit/blocks/BaseBlock.java +++ b/core/src/main/java/com/sk89q/worldedit/blocks/BaseBlock.java @@ -19,6 +19,7 @@ package com.sk89q.worldedit.blocks; +import com.boydti.fawe.FaweCache; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; @@ -125,6 +126,10 @@ public class BaseBlock extends Block implements TileEntityBlock { this(other.getId(), other.getData(), other.getNbtData()); } + public final int getCombined() { + return FaweCache.getCombined(this); + } + /** * Get the ID of the block. * 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 07d3f10c..b39ec961 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/BrushCommands.java @@ -31,7 +31,9 @@ import com.boydti.fawe.object.brush.CopyPastaBrush; import com.boydti.fawe.object.brush.ErodeBrush; import com.boydti.fawe.object.brush.FlattenBrush; import com.boydti.fawe.object.brush.HeightBrush; +import com.boydti.fawe.object.brush.LayerBrush; import com.boydti.fawe.object.brush.LineBrush; +import com.boydti.fawe.object.brush.PopulateSchem; import com.boydti.fawe.object.brush.RaiseBrush; import com.boydti.fawe.object.brush.RecurseBrush; import com.boydti.fawe.object.brush.ScatterBrush; @@ -98,6 +100,8 @@ import java.io.InputStream; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; +import java.util.ArrayList; +import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; @@ -525,6 +529,7 @@ public class BrushCommands { help = "Chooses the scatter brush.\n" + " The -o flag will overlay the block", + flags = "o", min = 1, max = 4 ) @@ -544,6 +549,62 @@ public class BrushCommands { player.print(BBC.getPrefix() + BBC.BRUSH_SCATTER.f(radius, points)); } + @Command( + aliases = { "populateschematic", "populateschem", "popschem", "pschem", "ps" }, + usage = " [radius=30] [points=5]", + desc = "Scatter a schematic on a surface", + help = + "Chooses the scatter schematic brush.\n" + + "The -r flag will apply random rotation", + flags = "r", + min = 2, + max = 4 + ) + @CommandPermissions("worldedit.brush.populateschematic") + public void scatterSchemBrush(Player player, EditSession editSession, LocalSession session, Mask mask, String clipboard, @Optional("30") double radius, @Optional("50") double density, @Switch('r') boolean rotate) throws WorldEditException { + worldEdit.checkMaxBrushRadius(radius); + BrushTool tool = session.getBrushTool(player); + tool.setSize(radius); + try { + ClipboardHolder[] clipboards = ClipboardFormat.SCHEMATIC.loadAllFromInput(player, player.getWorld().getWorldData(), clipboard, true); + if (clipboards == null) { + return; + } + Brush brush = new PopulateSchem(mask, clipboards, (int) density, rotate); + tool.setBrush(brush, "worldedit.brush.populateschematic", player); + player.print(BBC.getPrefix() + BBC.BRUSH_POPULATE.f(radius, density)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Command( + aliases = { "layer" }, + usage = " ...", + desc = "Scatter a schematic on a surface", + help = "Chooses the layer brush.", + min = 3, + max = 999 + ) + @CommandPermissions("worldedit.brush.layer") + public void surfaceLayer(Player player, EditSession editSession, LocalSession session, double radius, CommandContext args) throws WorldEditException { + worldEdit.checkMaxBrushRadius(radius); + BrushTool tool = session.getBrushTool(player); + tool.setSize(radius); + ParserContext parserContext = new ParserContext(); + parserContext.setActor(player); + parserContext.setWorld(player.getWorld()); + parserContext.setSession(session); + parserContext.setExtent(editSession); + List blocks = new ArrayList<>(); + for (int i = 1; i < args.argsLength(); i++) { + String arg = args.getString(i); + blocks.add(worldEdit.getBlockFactory().parseFromInput(arg, parserContext)); + } + tool.setBrush(new LayerBrush(blocks.toArray(new BaseBlock[blocks.size()])), "worldedit.brush.layer", player); + player.print(BBC.getPrefix() + BBC.BRUSH_LAYER.f(radius, args.getJoinedStrings(1))); + } + @Command( aliases = { "splatter", "splat" }, usage = " [radius=5] [seeds=1] [recursion=5] [solid=true]", @@ -852,6 +913,7 @@ public class BrushCommands { BrushTool tool = session.getBrushTool(player); String cmd = args.getJoinedStrings(1); tool.setBrush(new CommandBrush(cmd, radius), "worldedit.brush.copy", player); + tool.setSize(radius); player.print(BBC.getPrefix() + BBC.BRUSH_COMMAND.f(cmd)); } diff --git a/core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java b/core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java index 18f8084f..bcc9a5dd 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/GeneralCommands.java @@ -44,7 +44,6 @@ public class GeneralCommands { aliases = { "/tips", "tips" }, desc = "Toggle WorldEdit tips" ) - @CommandPermissions("fawe.use") public void tips(Player player, LocalSession session) throws WorldEditException { FawePlayer fp = FawePlayer.wrap(player); if (fp.toggle("fawe.tips")) { diff --git a/core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java b/core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java index a689fd24..983c0cd6 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/GenerationCommands.java @@ -21,6 +21,7 @@ package com.sk89q.worldedit.command; import com.boydti.fawe.command.FawePrimitiveBinding; import com.boydti.fawe.config.BBC; +import com.boydti.fawe.jnbt.anvil.generator.CavesGen; import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandPermissions; import com.sk89q.minecraft.util.commands.Logging; @@ -30,6 +31,7 @@ import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.function.mask.Mask; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.internal.annotation.Selection; import com.sk89q.worldedit.internal.expression.ExpressionException; @@ -66,6 +68,49 @@ public class GenerationCommands { this.worldEdit = worldEdit; } + @Command( + aliases = { "/caves [size=8] [freq=40] [rarity=7] [minY=8] [maxY=127] [sysFreq=1] [sysRarity=25] [pocketRarity=0] [pocketMin=0] [pocketMax=3]" }, + desc = "Generates caves", + help = "Generates a cave network" + ) + @CommandPermissions("worldedit.generation.cylinder") + @Logging(PLACEMENT) + public void caves(Player player, LocalSession session, EditSession editSession, @Selection Region region, @Optional("8") int size, @Optional("40") int frequency, @Optional("7") int rarity, @Optional("8") int minY, @Optional("127") int maxY, @Optional("1") int systemFrequency, @Optional("25") int individualRarity, @Optional("0") int pocketChance, @Optional("0") int pocketMin, @Optional("3") int pocketMax) throws WorldEditException, ParameterException { + CavesGen gen = new CavesGen(size, frequency, rarity, minY, maxY, systemFrequency, individualRarity, pocketChance, pocketMin, pocketMax); + editSession.generate(region, gen); + BBC.VISITOR_BLOCK.send(player, editSession.getBlockChangeCount()); + } + + // public void addOre(Mask mask, Pattern material, int size, int frequency, int rarity, int minY, int maxY) throws WorldEditException { + + @Command( + aliases = { "/ores" }, + desc = "Generates ores", + help = "Generates ores", + min = 1, + max = 1 + ) + @CommandPermissions("worldedit.generation.cylinder") + @Logging(PLACEMENT) + public void ores(Player player, LocalSession session, EditSession editSession, @Selection Region region, Mask mask) throws WorldEditException, ParameterException { + editSession.addOres(region, mask); + BBC.VISITOR_BLOCK.send(player, editSession.getBlockChangeCount()); + } + + @Command( + aliases = { "/ore " }, + desc = "Generates ores", + help = "Generates ores", + min = 7, + max = 7 + ) + @CommandPermissions("worldedit.generation.cylinder") + @Logging(PLACEMENT) + public void ore(Player player, LocalSession session, EditSession editSession, @Selection Region region, Mask mask, Pattern material, int size, int freq, int rarity, int minY, int maxY) throws WorldEditException, ParameterException { + editSession.addOre(region, mask, material, size, freq, rarity, minY, maxY); + BBC.VISITOR_BLOCK.send(player, editSession.getBlockChangeCount()); + } + @Command( aliases = { "/hcyl" }, usage = " [,] [height]", diff --git a/core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java b/core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java index 3c5db286..d5d335c9 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/RegionCommands.java @@ -313,24 +313,6 @@ public class RegionCommands { if (!FawePlayer.wrap(player).hasPermission("fawe.tips")) BBC.TIP_REPLACE_ID.or(BBC.TIP_REPLACE_LIGHT, BBC.TIP_REPLACE_MARKER, BBC.TIP_TAB_COMPLETE).send(player); } -// @Command( -// aliases = { "/mapreplace", "/mr", "/maprep" }, -// usage = " ", -// desc = "Replace all blocks in a selection 1:1 with the ", -// flags = "f", -// min = 1, -// max = 2 -// ) -// @CommandPermissions("worldedit.region.mapreplace") -// @Logging(REGION) -// public void mapreplace(Player player, EditSession editSession, @Selection Region region, @Optional Mask from, Pattern to) throws WorldEditException { -// if (from == null) { -// from = new ExistingBlockMask(editSession); -// } -// int affected = editSession.replaceBlocks(region, from, to); -// BBC.VISITOR_BLOCK.send(player, affected); -// } - @Command( aliases = { "/set" }, usage = "[pattern]", diff --git a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java index e3511268..f2c1d812 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java @@ -293,11 +293,13 @@ public class SchematicCommands { } } + // schem list all|mine|global page + @Command( aliases = {"list", "all", "ls"}, desc = "List saved schematics", min = 0, - max = 1, + max = 2, flags = "dnp", help = "List all schematics in the schematics directory\n" + " -d sorts by date, oldest first\n" + @@ -306,8 +308,12 @@ public class SchematicCommands { ) @CommandPermissions("worldedit.schematic.list") public void list(Actor actor, CommandContext args, @Switch('p') @Optional("1") int page) throws WorldEditException { + if (Settings.IMP.PATHS.PER_PLAYER_SCHEMATICS) { + + } + File dir = worldEdit.getWorkingDirectoryFile(worldEdit.getConfiguration().saveDir); - List fileList = allFiles(dir); + List fileList = allFiles(dir, true); if (fileList == null || fileList.isEmpty()) { BBC.SCHEMATIC_NONE.send(actor); @@ -367,13 +373,13 @@ public class SchematicCommands { } - private List allFiles(File root) { + private List allFiles(File root, boolean recursive) { File[] files = root.listFiles(); if (files == null) return null; List fileList = new ArrayList(); for (File f : files) { - if (f.isDirectory()) { - List subFiles = allFiles(f); + if (recursive && f.isDirectory()) { + List subFiles = allFiles(f, recursive); if (subFiles == null) continue; // empty subdir fileList.addAll(subFiles); } else { 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 effd2804..a815eb58 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java +++ b/core/src/main/java/com/sk89q/worldedit/command/SelectionCommands.java @@ -23,6 +23,7 @@ import com.boydti.fawe.config.BBC; import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.mask.IdMask; import com.boydti.fawe.object.regions.selector.FuzzyRegionSelector; +import com.boydti.fawe.object.regions.selector.PolyhedralRegionSelector; import com.google.common.base.Optional; import com.sk89q.minecraft.util.commands.Command; import com.sk89q.minecraft.util.commands.CommandContext; @@ -736,7 +737,7 @@ public class SelectionCommands { } else if (typeName.equalsIgnoreCase("cyl")) { selector = new CylinderRegionSelector(oldSelector); player.print(BBC.getPrefix() + BBC.SEL_CYLINDRICAL.s()); - } else if (typeName.equalsIgnoreCase("convex") || typeName.equalsIgnoreCase("hull") || typeName.equalsIgnoreCase("polyhedron")) { + } else if (typeName.equalsIgnoreCase("convex") || typeName.equalsIgnoreCase("hull")) { selector = new ConvexPolyhedralRegionSelector(oldSelector); player.print(BBC.getPrefix() + BBC.SEL_CONVEX_POLYHEDRAL.s()); Optional limit = ActorSelectorLimits.forActor(player).getPolyhedronVertexLimit(); @@ -744,6 +745,14 @@ public class SelectionCommands { player.print(BBC.getPrefix() + BBC.SEL_MAX.f(limit.get())); } player.print(BBC.getPrefix() + BBC.SEL_LIST.s()); + } else if (typeName.equalsIgnoreCase("polyhedral") || typeName.equalsIgnoreCase("polyhedron")) { + selector = new PolyhedralRegionSelector(player.getWorld()); + player.print(BBC.getPrefix() + BBC.SEL_CONVEX_POLYHEDRAL.s()); + Optional limit = ActorSelectorLimits.forActor(player).getPolyhedronVertexLimit(); + if (limit.isPresent()) { + player.print(BBC.getPrefix() + BBC.SEL_MAX.f(limit.get())); + } + player.print(BBC.getPrefix() + BBC.SEL_LIST.s()); } else if (typeName.startsWith("fuzzy") || typeName.startsWith("magic")) { Mask mask; if (typeName.length() > 6) { @@ -772,6 +781,7 @@ public class SelectionCommands { box.appendCommand("//sel sphere", "Select a sphere"); box.appendCommand("//sel cyl", "Select a cylinder"); box.appendCommand("//sel convex", "Select a convex polyhedral"); + box.appendCommand("//sel polyhedral", "Select a hollow polyhedral"); box.appendCommand("//sel fuzzy[=]", "Select all connected blocks"); player.printRaw(ColorCodeBuilder.asColorCodes(box)); diff --git a/core/src/main/java/com/sk89q/worldedit/command/composition/SelectionCommand.java b/core/src/main/java/com/sk89q/worldedit/command/composition/SelectionCommand.java index 325f859a..6aae4d1e 100644 --- a/core/src/main/java/com/sk89q/worldedit/command/composition/SelectionCommand.java +++ b/core/src/main/java/com/sk89q/worldedit/command/composition/SelectionCommand.java @@ -100,7 +100,7 @@ public class SelectionCommand extends SimpleCommand { FawePlayer fp = FawePlayer.wrap(player); FaweRegionExtent regionExtent = editSession.getRegionExtent(); - if (function instanceof BlockReplace && regionExtent == null) { + if (function instanceof BlockReplace && regionExtent == null || regionExtent.isGlobal()) { try { BlockReplace replace = ((BlockReplace) function); Field field = replace.getClass().getDeclaredField("pattern"); @@ -148,7 +148,6 @@ public class SelectionCommand extends SimpleCommand { } }); queue.enqueue(); - long start = System.currentTimeMillis(); BBC.OPERATION.send(actor, BBC.VISITOR_BLOCK.format(cuboid.getArea())); queue.flush(); return null; diff --git a/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java b/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java index c72fec4f..4c50b5ad 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/factory/DefaultMaskParser.java @@ -164,6 +164,7 @@ public class DefaultMaskParser extends FaweParser { private Mask getBlockMaskComponent(List masks, String input, ParserContext context) throws InputParseException { Extent extent = Request.request().getExtent(); + if (extent == null) extent = context.getExtent(); final char firstChar = input.charAt(0); switch (firstChar) { case '#': @@ -303,7 +304,7 @@ public class DefaultMaskParser extends FaweParser { y1 = (Expression.compile(split[0]).evaluate()); y2 = (Expression.compile(split[1]).evaluate()); } - return new AngleMask(Request.request().getEditSession(), y1, y2); + return new AngleMask(extent, y1, y2); } catch (NumberFormatException | ExpressionException e) { throw new SuggestInputParseException(input, "/:"); } @@ -385,7 +386,7 @@ public class DefaultMaskParser extends FaweParser { try { Expression exp = Expression.compile(input.substring(1), "x", "y", "z"); WorldEditExpressionEnvironment env = new WorldEditExpressionEnvironment( - Request.request().getEditSession(), Vector.ONE, Vector.ZERO); + Request.request().getEditSession (), Vector.ONE, Vector.ZERO); exp.setEnvironment(env); return new ExpressionMask(exp); } catch (ExpressionException e) { diff --git a/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java b/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java index e6115c34..68f8d3a6 100644 --- a/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java +++ b/core/src/main/java/com/sk89q/worldedit/extension/factory/HashTagPatternParser.java @@ -22,6 +22,7 @@ import com.boydti.fawe.object.pattern.RelativePattern; import com.boydti.fawe.object.pattern.SolidRandomOffsetPattern; import com.boydti.fawe.object.pattern.SurfaceRandomOffsetPattern; import com.boydti.fawe.util.MainUtil; +import com.boydti.fawe.util.MathMan; import com.boydti.fawe.util.StringMan; import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.EmptyClipboardException; @@ -122,7 +123,7 @@ public class HashTagPatternParser extends FaweParser { switch (split2[0].toLowerCase()) { case "#*": case "#existing": { - return new ExistingPattern(Request.request().getEditSession()); + return new ExistingPattern(Request.request().getExtent()); } case "#clipboard": case "#copy": { @@ -151,7 +152,7 @@ public class HashTagPatternParser extends FaweParser { throw new InputParseException("#fullcopy:"); } boolean random = split2.length == 3 && split2[2].equalsIgnoreCase("true"); - return new RandomFullClipboardPattern(Request.request().getEditSession(), context.requireWorld().getWorldData(), clipboards, random); + return new RandomFullClipboardPattern(Request.request().getExtent(), context.requireWorld().getWorldData(), clipboards, random); } catch (IOException e) { throw new RuntimeException(e); @@ -159,7 +160,7 @@ public class HashTagPatternParser extends FaweParser { } ClipboardHolder holder = session.getClipboard(); Clipboard clipboard = holder.getClipboard(); - return new FullClipboardPattern(Request.request().getEditSession(), clipboard); + return new FullClipboardPattern(Request.request().getExtent(), clipboard); } catch (EmptyClipboardException e) { throw new InputParseException("To use #fullcopy, please first copy something to your clipboard"); } @@ -168,17 +169,20 @@ public class HashTagPatternParser extends FaweParser { } } case "#id": { - return new IdPattern(Request.request().getEditSession(), catchSuggestion(input, rest, context)); + return new IdPattern(Request.request().getExtent(), catchSuggestion(input, rest, context)); } case "#data": { - return new DataPattern(Request.request().getEditSession(), catchSuggestion(input, rest, context)); + return new DataPattern(Request.request().getExtent(), catchSuggestion(input, rest, context)); } case "#biome": { + if (MathMan.isInteger(rest)) { + return new BiomePattern(Request.request().getExtent(), new BaseBiome(Integer.parseInt(rest))); + } World world = context.requireWorld(); BiomeRegistry biomeRegistry = world.getWorldData().getBiomeRegistry(); List knownBiomes = biomeRegistry.getBiomes(); BaseBiome biome = Biomes.findBiomeByName(knownBiomes, rest, biomeRegistry); - return new BiomePattern(Request.request().getEditSession(), biome); + return new BiomePattern(Request.request().getExtent(), biome); } case "#~": case "#r": @@ -272,6 +276,9 @@ public class HashTagPatternParser extends FaweParser { } case "#l": case "#linear": { + if (rest.startsWith("\"") && rest.endsWith("\"")) { + rest = rest.substring(1, rest.length() - 1); + } ArrayList patterns = new ArrayList<>(); for (String token : StringMan.split(rest, ',')) { patterns.add(catchSuggestion(input, token, context)); @@ -283,6 +290,9 @@ public class HashTagPatternParser extends FaweParser { } case "#l3d": case "#linear3d": { + if (rest.startsWith("\"") && rest.endsWith("\"")) { + rest = rest.substring(1, rest.length() - 1); + } ArrayList patterns = new ArrayList<>(); for (String token : StringMan.split(rest, ',')) { patterns.add(catchSuggestion(input, token, context)); diff --git a/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java b/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java index c505ed67..363510e5 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/AbstractDelegateExtent.java @@ -117,6 +117,7 @@ public abstract class AbstractDelegateExtent implements LightingExtent { private MutableBlockVector mutable = new MutableBlockVector(0,0,0); + @Override public BaseBlock getLazyBlock(int x, int y, int z) { mutable.mutX(x); mutable.mutY(y); @@ -129,6 +130,7 @@ public abstract class AbstractDelegateExtent implements LightingExtent { return extent.getLazyBlock(position); } + @Override public boolean setBlock(int x, int y, int z, BaseBlock block) throws WorldEditException { mutable.mutX(x); mutable.mutY(y); diff --git a/core/src/main/java/com/sk89q/worldedit/extent/Extent.java b/core/src/main/java/com/sk89q/worldedit/extent/Extent.java new file mode 100644 index 00000000..3a3773c9 --- /dev/null +++ b/core/src/main/java/com/sk89q/worldedit/extent/Extent.java @@ -0,0 +1,191 @@ +package com.sk89q.worldedit.extent; + +import com.boydti.fawe.FaweCache; +import com.boydti.fawe.jnbt.anvil.generator.CavesGen; +import com.boydti.fawe.jnbt.anvil.generator.GenBase; +import com.boydti.fawe.jnbt.anvil.generator.OreGen; +import com.boydti.fawe.jnbt.anvil.generator.Resource; +import com.boydti.fawe.jnbt.anvil.generator.SchemGen; +import com.boydti.fawe.object.PseudoRandom; +import com.sk89q.worldedit.MutableBlockVector; +import com.sk89q.worldedit.Vector; +import com.sk89q.worldedit.Vector2D; +import com.sk89q.worldedit.WorldEditException; +import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.blocks.BlockID; +import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; +import com.sk89q.worldedit.function.mask.Mask; +import com.sk89q.worldedit.function.operation.Operation; +import com.sk89q.worldedit.function.pattern.BlockPattern; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.session.ClipboardHolder; +import com.sk89q.worldedit.util.Location; +import com.sk89q.worldedit.world.registry.WorldData; +import java.util.ArrayList; +import java.util.List; +import javax.annotation.Nullable; + +public interface Extent extends InputExtent, OutputExtent { + + Vector getMinimumPoint(); + + Vector getMaximumPoint(); + + default List getEntities(Region region) { + return new ArrayList<>(); + } + + default List getEntities() { + return new ArrayList<>(); + } + + default @Nullable Entity createEntity(Location location, BaseEntity entity) { + throw new UnsupportedOperationException(getClass() + " does not support entity creation!"); + } + + @Override + default BaseBlock getLazyBlock(Vector position) { + return getBlock(position); + } + + default public boolean setBlock(int x, int y, int z, BaseBlock block) throws WorldEditException { + return setBlock(MutableBlockVector.get(x, y, z), block); + } + + @Nullable + @Override + default Operation commit() { + return null; + } + + default BaseBlock getLazyBlock(int x, int y, int z) { + return getLazyBlock(MutableBlockVector.get(x, y, z)); + } + + default public int getNearestSurfaceLayer(int x, int z, int y, int minY, int maxY) { + int clearanceAbove = maxY - y; + int clearanceBelow = y - minY; + int clearance = Math.min(clearanceAbove, clearanceBelow); + + BaseBlock block = getLazyBlock(x, y, z); + boolean state = FaweCache.isLiquidOrGas(block.getId()); + int data1 = block.getData(); + int data2 = block.getData(); + int offset = state ? 0 : 1; + for (int d = 0; d <= clearance; d++) { + int y1 = y + d; + block = getLazyBlock(x, y1, z); + if (FaweCache.isLiquidOrGas(block.getId()) != state) { + return ((y1 - offset) << 3) - (7 - (state ? block.getData() : data1)); + } + data1 = block.getData(); + int y2 = y - d; + block = getLazyBlock(x, y2, z); + if (FaweCache.isLiquidOrGas(block.getId()) != state) { + return ((y2 + offset) << 3) - (7 - (state ? block.getData() : data2)); + } + data2 = block.getData(); + } + if (clearanceAbove != clearanceBelow) { + if (clearanceAbove < clearanceBelow) { + for (int layer = y - clearance - 1; layer >= minY; layer--) { + block = getLazyBlock(x, layer, z); + if (FaweCache.isLiquidOrGas(block.getId()) != state) { + return ((layer + offset) << 3) - (7 - (state ? block.getData() : data1)); + } + data1 = block.getData(); + } + } else { + for (int layer = y + clearance + 1; layer <= maxY; layer++) { + block = getLazyBlock(x, layer, z); + if (FaweCache.isLiquidOrGas(block.getId()) != state) { + return ((layer - offset) << 3) - (7 - (state ? block.getData() : data2)); + } + data2 = block.getData(); + } + } + } + return maxY << 4; + } + + public default int getNearestSurfaceTerrainBlock(int x, int z, int y, int minY, int maxY) { + int clearanceAbove = maxY - y; + int clearanceBelow = y - minY; + int clearance = Math.min(clearanceAbove, clearanceBelow); + BaseBlock block = getLazyBlock(x, y, z); + boolean state = FaweCache.canPassThrough(block.getId(), block.getData()); + int offset = state ? 0 : 1; + for (int d = 0; d <= clearance; d++) { + int y1 = y + d; + block = getLazyBlock(x, y1, z); + if (FaweCache.canPassThrough(block.getId(), block.getData()) != state) return y1 - offset; + int y2 = y - d; + block = getLazyBlock(x, y2, z); + if (FaweCache.canPassThrough(block.getId(), block.getData()) != state) return y2 + offset; + } + if (clearanceAbove != clearanceBelow) { + if (clearanceAbove < clearanceBelow) { + for (int layer = y - clearance - 1; layer >= minY; layer--) { + block = getLazyBlock(x, layer, z); + if (FaweCache.canPassThrough(block.getId(), block.getData()) != state) return layer + offset; + } + } else { + for (int layer = y + clearance + 1; layer <= maxY; layer++) { + block = getLazyBlock(x, layer, z); + if (FaweCache.canPassThrough(block.getId(), block.getData()) != state) return layer - offset; + } + } + } + return maxY; + } + + + default public void addCaves(Region region) throws WorldEditException { + generate(region, new CavesGen(8)); + } + + public default void generate(Region region, GenBase gen) throws WorldEditException { + for (Vector2D chunkPos : region.getChunks()) { + gen.generate(chunkPos, this); + } + } + + default public void addSchems(Region region, Mask mask, WorldData worldData, ClipboardHolder[] clipboards, int rarity, boolean rotate) throws WorldEditException{ + spawnResource(region, new SchemGen(mask, this, worldData, clipboards, rotate), rarity, 1); + } + + default public void spawnResource(Region region, Resource gen, int rarity, int frequency) throws WorldEditException { + PseudoRandom random = new PseudoRandom(); + for (Vector2D chunkPos : region.getChunks()) { + for (int i = 0; i < frequency; i++) { + if (random.nextInt(101) > rarity) { + continue; + } + int x = (chunkPos.getBlockX() << 4) + PseudoRandom.random.nextInt(16); + int z = (chunkPos.getBlockZ() << 4) + PseudoRandom.random.nextInt(16); + gen.spawn(random, x, z); + } + } + } + + default public void addOre(Region region, Mask mask, Pattern material, int size, int frequency, int rarity, int minY, int maxY) throws WorldEditException { + spawnResource(region, new OreGen(this, mask, material, size, minY, maxY), rarity, frequency); + } + + default public void addOres(Region region, Mask mask) throws WorldEditException { + addOre(region, mask, new BlockPattern(BlockID.DIRT), 20, 2, 100, 0, 255); + addOre(region, mask, new BlockPattern(BlockID.GRAVEL), 20, 1, 100, 0, 255); + addOre(region, mask, new BlockPattern(BlockID.STONE, 1), 20, 2, 100, 0, 79); + addOre(region, mask, new BlockPattern(BlockID.STONE, 3), 20, 2, 100, 0, 79); + addOre(region, mask, new BlockPattern(BlockID.STONE, 5), 20, 2, 100, 0, 79); + addOre(region, mask, new BlockPattern(BlockID.COAL_ORE), 17, 20, 100, 0, 127); + addOre(region, mask, new BlockPattern(BlockID.IRON_ORE), 9, 20, 100, 0, 63); + addOre(region, mask, new BlockPattern(BlockID.GOLD_ORE), 9, 2, 100, 0, 31); + addOre(region, mask, new BlockPattern(BlockID.REDSTONE_ORE), 8, 8, 100, 0, 15); + addOre(region, mask, new BlockPattern(BlockID.DIAMOND_ORE), 8, 1, 100, 0, 15); + addOre(region, mask, new BlockPattern(BlockID.LAPIS_LAZULI_ORE), 7, 1, 100, 0, 15); + addOre(region, mask, new BlockPattern(BlockID.EMERALD_ORE), 5, 1, 100, 4, 31); + } +} diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java index 6f409cd7..dc5b09c2 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java @@ -52,7 +52,7 @@ import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.world.registry.WorldData; -import java.io.BufferedInputStream; +import it.unimi.dsi.fastutil.io.FastBufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataInputStream; import java.io.File; @@ -94,8 +94,8 @@ public enum ClipboardFormat { if (inputStream instanceof FileInputStream) { inputStream = new ResettableFileInputStream((FileInputStream) inputStream); } - BufferedInputStream buffered = new BufferedInputStream(inputStream); - NBTInputStream nbtStream = new NBTInputStream(new BufferedInputStream(new GZIPInputStream(buffered))); + FastBufferedInputStream buffered = new FastBufferedInputStream(inputStream); + NBTInputStream nbtStream = new NBTInputStream(new FastBufferedInputStream(new GZIPInputStream(buffered))); SchematicReader input = new SchematicReader(nbtStream); input.setUnderlyingStream(inputStream); return input; @@ -151,8 +151,8 @@ public enum ClipboardFormat { STRUCTURE(new AbstractClipboardFormat("STRUCTURE", "structure", "nbt") { @Override public ClipboardReader getReader(InputStream inputStream) throws IOException { - inputStream = new BufferedInputStream(inputStream); - NBTInputStream nbtStream = new NBTInputStream(new BufferedInputStream(new GZIPInputStream(inputStream))); + inputStream = new FastBufferedInputStream(inputStream); + NBTInputStream nbtStream = new NBTInputStream(new FastBufferedInputStream(new GZIPInputStream(inputStream))); return new StructureFormat(nbtStream); } @@ -340,10 +340,14 @@ public enum ClipboardFormat { dir = new File(worldEdit.getWorkingDirectoryFile(config.saveDir), input); } } - if (!dir.exists() || !dir.isDirectory()) { + if (!dir.exists()) { if (message) BBC.SCHEMATIC_NOT_FOUND.send(player, input); return null; } + if (!dir.isDirectory()) { + ByteSource source = Files.asByteSource(dir); + return new ClipboardHolder[] {new LazyClipboardHolder(source, this, worldData, null)}; + } ClipboardHolder[] clipboards = loadAllFromDirectory(dir, worldData); if (clipboards.length < 1) { if (message) BBC.SCHEMATIC_NOT_FOUND.send(player, input); diff --git a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicWriter.java b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicWriter.java index e1631203..eae86179 100644 --- a/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicWriter.java +++ b/core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicWriter.java @@ -146,16 +146,16 @@ public class SchematicWriter implements ClipboardWriter { public void write(NBTOutputStream out) throws IOException { int volume = width * height * length; - out.writeNamedTag("Width", new ShortTag((short) width)); - out.writeNamedTag("Length", new ShortTag((short) length)); - out.writeNamedTag("Height", new ShortTag((short) height)); - out.writeNamedTag("Materials", new StringTag("Alpha")); - out.writeNamedTag("WEOriginX", new IntTag(min.getBlockX())); - out.writeNamedTag("WEOriginY", new IntTag(min.getBlockY())); - out.writeNamedTag("WEOriginZ", new IntTag(min.getBlockZ())); - out.writeNamedTag("WEOffsetX", new IntTag(offset.getBlockX())); - out.writeNamedTag("WEOffsetY", new IntTag(offset.getBlockY())); - out.writeNamedTag("WEOffsetZ", new IntTag(offset.getBlockZ())); + out.writeNamedTag("Width", ((short) width)); + out.writeNamedTag("Length", ((short) length)); + out.writeNamedTag("Height", ((short) height)); + out.writeNamedTag("Materials", ("Alpha")); + out.writeNamedTag("WEOriginX", (min.getBlockX())); + out.writeNamedTag("WEOriginY", (min.getBlockY())); + out.writeNamedTag("WEOriginZ", (min.getBlockZ())); + out.writeNamedTag("WEOffsetX", (offset.getBlockX())); + out.writeNamedTag("WEOffsetY", (offset.getBlockY())); + out.writeNamedTag("WEOffsetZ", (offset.getBlockZ())); out.writeNamedTagName("Blocks", NBTConstants.TYPE_BYTE_ARRAY); out.getOutputStream().writeInt(volume); @@ -217,7 +217,7 @@ public class SchematicWriter implements ClipboardWriter { final List tileEntities = clipboard.IMP.getTileEntities(); out.writeNamedTag("TileEntities", new ListTag(CompoundTag.class, tileEntities)); } else { - out.writeNamedTag("TileEntities", new ListTag(CompoundTag.class, new ArrayList())); + out.writeNamedEmptyList("TileEntities"); } List entities = new ArrayList(); @@ -242,7 +242,11 @@ public class SchematicWriter implements ClipboardWriter { entities.add(entityTag); } } - out.writeNamedTag("Entities", new ListTag(CompoundTag.class, entities)); + if (entities.isEmpty()) { + out.writeNamedEmptyList("Entities"); + } else { + out.writeNamedTag("Entities", new ListTag(CompoundTag.class, entities)); + } } }); outputStream.flush(); diff --git a/core/src/main/java/com/sk89q/worldedit/function/mask/BlockMask.java b/core/src/main/java/com/sk89q/worldedit/function/mask/BlockMask.java index 0bf4b286..04aa77ec 100644 --- a/core/src/main/java/com/sk89q/worldedit/function/mask/BlockMask.java +++ b/core/src/main/java/com/sk89q/worldedit/function/mask/BlockMask.java @@ -115,6 +115,28 @@ public class BlockMask extends AbstractExtentMask { return computedLegacyList; } + public Collection getInverseBlocks() { + if (computedLegacyList != null) { + return computedLegacyList; + } + computedLegacyList = new LinkedHashSet<>(); + for (int id = 0; id < FaweCache.getId(blocks.length); id++) { + boolean all = true; + ArrayList tmp = new ArrayList(16); + for (int data = 0; data < 16; data++) { + if (!blocks[FaweCache.getCombined(id, data)]) { + tmp.add(FaweCache.getBlock(id, data)); + } + } + if (tmp.size() == 16) { + computedLegacyList.add(new BaseBlock(id, -1)); + } else { + computedLegacyList.addAll(tmp); + } + } + return computedLegacyList; + } + @Override public String toString() { return StringMan.getString(getBlocks()); diff --git a/core/src/main/java/com/sk89q/worldedit/function/pattern/BlockPattern.java b/core/src/main/java/com/sk89q/worldedit/function/pattern/BlockPattern.java index 7479b788..e7e11fb2 100644 --- a/core/src/main/java/com/sk89q/worldedit/function/pattern/BlockPattern.java +++ b/core/src/main/java/com/sk89q/worldedit/function/pattern/BlockPattern.java @@ -1,5 +1,6 @@ package com.sk89q.worldedit.function.pattern; +import com.boydti.fawe.FaweCache; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.blocks.BaseBlock; @@ -14,6 +15,14 @@ public class BlockPattern implements Pattern { this.block = block; } + public BlockPattern(int id) { + this.block = FaweCache.getBlock(id, 0); + } + + public BlockPattern(int id, int data) { + this.block = FaweCache.getBlock(id, data); + } + @Override public BaseBlock apply(Vector position) { return block; diff --git a/core/src/main/java/com/sk89q/worldedit/function/pattern/RandomPattern.java b/core/src/main/java/com/sk89q/worldedit/function/pattern/RandomPattern.java index fd6da837..232985a0 100644 --- a/core/src/main/java/com/sk89q/worldedit/function/pattern/RandomPattern.java +++ b/core/src/main/java/com/sk89q/worldedit/function/pattern/RandomPattern.java @@ -6,6 +6,7 @@ import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.extent.Extent; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @@ -19,7 +20,7 @@ public class RandomPattern extends AbstractPattern { private Map weights = new HashMap<>(); private RandomCollection collection; - private Set patterns; + private LinkedHashSet patterns = new LinkedHashSet<>(); /** * Add a pattern to the weight list of patterns. @@ -34,7 +35,11 @@ public class RandomPattern extends AbstractPattern { checkNotNull(pattern); weights.put(pattern, chance); collection = RandomCollection.of(weights); - this.patterns = weights.keySet(); + this.patterns.add(pattern); + } + + public Set getPatterns() { + return patterns; } @Override diff --git a/core/src/main/java/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java b/core/src/main/java/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java index 5852b1a8..502334b3 100644 --- a/core/src/main/java/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java +++ b/core/src/main/java/com/sk89q/worldedit/function/visitor/BreadthFirstSearch.java @@ -58,6 +58,7 @@ public abstract class BreadthFirstSearch implements Operation { private BlockVectorSet visited; private final MappedFaweQueue mFaweQueue; private BlockVectorSet queue; + private int currentDepth = 0; private final int maxDepth; private int affected = 0; private int maxBranch = Integer.MAX_VALUE; @@ -107,6 +108,12 @@ public abstract class BreadthFirstSearch implements Operation { } } + public void resetVisited() { + queue.clear(); + visited.clear(); + affected = 0; + } + public void setVisited(BlockVectorSet set) { this.visited = set; } @@ -131,7 +138,7 @@ public abstract class BreadthFirstSearch implements Operation { IntegerTrio[] dirs = getIntDirections(); BlockVectorSet tempQueue = new BlockVectorSet(); BlockVectorSet chunkLoadSet = new BlockVectorSet(); - for (int layer = 0; !queue.isEmpty() && layer <= maxDepth; layer++) { + for (currentDepth = 0; !queue.isEmpty() && currentDepth <= maxDepth; currentDepth++) { if (mFaweQueue != null && Settings.IMP.QUEUE.PRELOAD_CHUNKS > 1) { int cx = Integer.MIN_VALUE; int cz = Integer.MIN_VALUE; @@ -176,7 +183,7 @@ public abstract class BreadthFirstSearch implements Operation { } } } - if (layer == maxDepth) { + if (currentDepth == maxDepth) { break; } int size = queue.size(); @@ -190,6 +197,10 @@ public abstract class BreadthFirstSearch implements Operation { return null; } + public int getDepth() { + return currentDepth; + } + @Override public void addStatusMessages(List messages) { messages.add(BBC.VISITOR_BLOCK.format(getAffected())); diff --git a/core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java b/core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java index 49429387..a17d994a 100644 --- a/core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java +++ b/core/src/main/java/com/sk89q/worldedit/regions/CuboidRegion.java @@ -474,7 +474,7 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion { if (!hasNext) { throw new NoSuchElementException("End of iterator") { @Override - public synchronized Throwable fillInStackTrace() { + public Throwable fillInStackTrace() { return this; } }; @@ -539,7 +539,7 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion { if (!hasNext) { throw new NoSuchElementException("End of iterator") { @Override - public synchronized Throwable fillInStackTrace() { + public Throwable fillInStackTrace() { return this; } }; @@ -561,7 +561,6 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion { }; } - @Override public Iterable asFlatRegion() { return new Iterable() { @@ -576,7 +575,7 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion { @Override public boolean hasNext() { - return (nextZ != Integer.MIN_VALUE); + return (nextZ != Integer.MAX_VALUE); } @Override @@ -586,7 +585,16 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion { if (++nextX > max.getBlockX()) { nextX = min.getBlockX(); if (++nextZ > max.getBlockZ()) { - nextZ = Integer.MIN_VALUE; + if (nextZ == Integer.MIN_VALUE) { + throw new NoSuchElementException("End of iterator") { + @Override + public Throwable fillInStackTrace() { + return this; + } + }; + } + nextZ = Integer.MAX_VALUE; + nextX = Integer.MAX_VALUE; } } return answer; @@ -623,6 +631,12 @@ public class CuboidRegion extends AbstractRegion implements FlatRegion { return new CuboidRegion(region.getMinimumPoint(), region.getMaximumPoint()); } + public static boolean contains(CuboidRegion region) { + Vector min = region.getMinimumPoint(); + Vector max = region.getMaximumPoint(); + return region.contains(min.getBlockX(), min.getBlockY(), min.getBlockZ()) && region.contains(max.getBlockX(), max.getBlockY(), max.getBlockZ()); + } + /** * Make a cuboid from the center. * diff --git a/core/src/main/java/com/sk89q/worldedit/world/registry/BundledBlockData.java b/core/src/main/java/com/sk89q/worldedit/world/registry/BundledBlockData.java index 233daa64..161dee01 100644 --- a/core/src/main/java/com/sk89q/worldedit/world/registry/BundledBlockData.java +++ b/core/src/main/java/com/sk89q/worldedit/world/registry/BundledBlockData.java @@ -143,9 +143,8 @@ public class BundledBlockData { } idMap.put(entry.id, entry); String id = (entry.id.contains(":") ? entry.id.split(":")[1] : entry.id).toLowerCase().replace(" ", "_"); - if (!idMap.containsKey(id)) { - idMap.put(id, entry); - } + localIdMap.putIfAbsent(id, entry); + idMap.putIfAbsent(id, entry); legacyMap[entry.legacyId] = entry; if (entry.states == null) { return true; diff --git a/forge110/build.gradle b/forge110/build.gradle index 4376197b..99fd736e 100644 --- a/forge110/build.gradle +++ b/forge110/build.gradle @@ -58,8 +58,8 @@ shadowJar { relocate 'org.yaml.snakeyaml', 'com.boydti.fawe.yaml' dependencies { include(dependency('com.github.luben:zstd-jni:1.1.1')) - include(dependency('org.javassist:javassist:3.22.0-CR1')) - include(dependency('co.aikar:fastutil-lite:1.0')) +// include(dependency('org.javassist:javassist:3.22.0-CR1')) + include(dependency('it.unimi.dsi:fastutil:6.5.1')) include(dependency(':core')) include(dependency('org.yaml:snakeyaml:1.16')) } diff --git a/forge111/build.gradle b/forge111/build.gradle index db87e419..e38e0cc6 100644 --- a/forge111/build.gradle +++ b/forge111/build.gradle @@ -58,8 +58,8 @@ shadowJar { relocate 'org.yaml.snakeyaml', 'com.boydti.fawe.yaml' dependencies { include(dependency('com.github.luben:zstd-jni:1.1.1')) - include(dependency('org.javassist:javassist:3.22.0-CR1')) - include(dependency('co.aikar:fastutil-lite:1.0')) +// include(dependency('org.javassist:javassist:3.22.0-CR1')) + include(dependency('it.unimi.dsi:fastutil:6.5.1')) include(dependency(':core')) include(dependency('org.yaml:snakeyaml:1.16')) } diff --git a/forge1710/build.gradle b/forge1710/build.gradle index ab68272b..9d90f76c 100644 --- a/forge1710/build.gradle +++ b/forge1710/build.gradle @@ -49,8 +49,8 @@ shadowJar { relocate 'org.yaml.snakeyaml', 'com.boydti.fawe.yaml' dependencies { include(dependency('com.github.luben:zstd-jni:1.1.1')) - include(dependency('org.javassist:javassist:3.22.0-CR1')) - include(dependency('co.aikar:fastutil-lite:1.0')) +// include(dependency('org.javassist:javassist:3.22.0-CR1')) + include(dependency('it.unimi.dsi:fastutil:6.5.1')) include(dependency(':core')) include(dependency('org.yaml:snakeyaml:1.16')) } diff --git a/forge189/build.gradle b/forge189/build.gradle index 0d99fdcc..48dd6197 100644 --- a/forge189/build.gradle +++ b/forge189/build.gradle @@ -58,8 +58,8 @@ shadowJar { relocate 'org.yaml.snakeyaml', 'com.boydti.fawe.yaml' dependencies { include(dependency('com.github.luben:zstd-jni:1.1.1')) - include(dependency('org.javassist:javassist:3.22.0-CR1')) - include(dependency('co.aikar:fastutil-lite:1.0')) +// include(dependency('org.javassist:javassist:3.22.0-CR1')) + include(dependency('it.unimi.dsi:fastutil:6.5.1')) include(dependency(':core')) include(dependency('org.yaml:snakeyaml:1.16')) } diff --git a/forge194/build.gradle b/forge194/build.gradle index 045233e2..d01313a9 100644 --- a/forge194/build.gradle +++ b/forge194/build.gradle @@ -57,8 +57,8 @@ shadowJar { relocate 'org.yaml.snakeyaml', 'com.boydti.fawe.yaml' dependencies { include(dependency('com.github.luben:zstd-jni:1.1.1')) - include(dependency('org.javassist:javassist:3.22.0-CR1')) - include(dependency('co.aikar:fastutil-lite:1.0')) +// include(dependency('org.javassist:javassist:3.22.0-CR1')) + include(dependency('it.unimi.dsi:fastutil:6.5.1')) include(dependency(':core')) include(dependency('org.yaml:snakeyaml:1.16')) } diff --git a/heightmap.png b/heightmap.png new file mode 100644 index 00000000..930f9e91 Binary files /dev/null and b/heightmap.png differ diff --git a/nukkit/build.gradle b/nukkit/build.gradle index 94663142..4afa28ba 100644 --- a/nukkit/build.gradle +++ b/nukkit/build.gradle @@ -23,8 +23,8 @@ jar.enabled = false shadowJar { dependencies { include(dependency('com.github.luben:zstd-jni:1.1.1')) - include(dependency('org.javassist:javassist:3.22.0-CR1')) - include(dependency('co.aikar:fastutil-lite:1.0')) +// include(dependency('org.javassist:javassist:3.22.0-CR1')) + include(dependency('it.unimi.dsi:fastutil:6.5.1')) include(dependency(name: 'worldedit-core-6.1.4-SNAPSHOT-dist')) include(dependency('com.google.code.gson:gson:2.2.4')) include(dependency('org.yaml:snakeyaml:1.16')) diff --git a/region/r.0.-1.mca b/region/r.0.-1.mca new file mode 100644 index 00000000..5381555c Binary files /dev/null and b/region/r.0.-1.mca differ diff --git a/settings.gradle b/settings.gradle index b35b3553..09d30257 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,3 @@ rootProject.name = 'FastAsyncWorldEdit' -include 'core', 'bukkit', 'favs', 'nukkit', 'sponge', 'forge1710', 'forge189', 'forge194', 'forge110', 'forge111' \ No newline at end of file +include 'core', 'bukkit'//, 'favs', 'nukkit', 'sponge', 'forge1710', 'forge189', 'forge194', 'forge110', 'forge111' \ No newline at end of file diff --git a/sponge/build.gradle b/sponge/build.gradle index 10928921..1a54380d 100644 --- a/sponge/build.gradle +++ b/sponge/build.gradle @@ -76,8 +76,8 @@ shadowJar { dependencies { include(dependency(':core')) include(dependency('com.github.luben:zstd-jni:1.1.1')) - include(dependency('org.javassist:javassist:3.22.0-CR1')) - include(dependency('co.aikar:fastutil-lite:1.0')) +// include(dependency('org.javassist:javassist:3.22.0-CR1')) + include(dependency('it.unimi.dsi:fastutil:6.5.1')) include(dependency(name: 'worldedit-core-6.1.7-SNAPSHOT-dist')) include(dependency('com.sk89q.worldedit:worldedit-sponge:6.1.7-SNAPSHOT')) include(dependency('org.yaml:snakeyaml:1.16'))