From 5b5336cc8363ed756f25dd8861ed569322dcd9e6 Mon Sep 17 00:00:00 2001 From: Jesse Boyd Date: Tue, 18 Sep 2018 12:49:33 +1000 Subject: [PATCH] some fixes Use sponge schematic format instead of structure block Fix VS undo running on main thread Fix missing sections when setting blocks --- .../com/thevoxelbox/voxelsniper/Sniper.java | 33 +-- .../voxelsniper/VoxelSniperListener.java | 51 ++-- .../adapter/v1_13_1/Spigot_v1_13_R2.java | 2 +- .../fawe/bukkit/wrapper/AsyncBlock.java | 19 +- .../com/boydti/fawe/jnbt/NBTStreamer.java | 5 + .../fawe/object/clipboard/FaweClipboard.java | 6 +- .../object/clipboard/LazyClipboardHolder.java | 8 +- .../object/clipboard/ReadOnlyClipboard.java | 4 +- .../object/clipboard/WorldCopyClipboard.java | 23 +- .../object/clipboard/WorldCutClipboard.java | 16 +- .../java/com/boydti/fawe/util/IOUtil.java | 39 ++- .../java/com/sk89q/jnbt/NBTInputStream.java | 14 +- .../java/com/sk89q/jnbt/NBTOutputStream.java | 4 +- .../worldedit/command/SchematicCommands.java | 2 +- .../com/sk89q/worldedit/extent/Extent.java | 15 + .../extent/clipboard/io/ClipboardFormat.java | 10 +- .../extent/clipboard/io/ClipboardReader.java | 6 +- .../clipboard/io/SpongeSchematicReader.java | 275 ++++++++++-------- .../clipboard/io/SpongeSchematicWriter.java | 221 +++++++------- 19 files changed, 431 insertions(+), 322 deletions(-) diff --git a/favs/src/main/java/com/thevoxelbox/voxelsniper/Sniper.java b/favs/src/main/java/com/thevoxelbox/voxelsniper/Sniper.java index 0c471f446..4c8e102a9 100644 --- a/favs/src/main/java/com/thevoxelbox/voxelsniper/Sniper.java +++ b/favs/src/main/java/com/thevoxelbox/voxelsniper/Sniper.java @@ -512,26 +512,21 @@ public class Sniper { public void undo(int amount) { FawePlayer fp = FawePlayer.wrap(getPlayer()); - if (!fp.runAction(new Runnable() { - @Override - public void run() { - int count = 0; - for (int i = 0; i < amount; i++) { - EditSession es = fp.getSession().undo(null, fp.getPlayer()); - if (es == null) { - break; - } else { - es.flushQueue(); - } - count++; - } - if (count > 0) { - BBC.COMMAND_UNDO_SUCCESS.send(fp); - } else { - BBC.COMMAND_UNDO_ERROR.send(fp); - } + int count = 0; + for (int i = 0; i < amount; i++) { + EditSession es = fp.getSession().undo(null, fp.getPlayer()); + if (es == null) { + break; + } else { + es.flushQueue(); } - }, true, false)); + count++; + } + if (count > 0) { + BBC.COMMAND_UNDO_SUCCESS.send(fp); + } else { + BBC.COMMAND_UNDO_ERROR.send(fp); + } } public void reset(String toolId) { diff --git a/favs/src/main/java/com/thevoxelbox/voxelsniper/VoxelSniperListener.java b/favs/src/main/java/com/thevoxelbox/voxelsniper/VoxelSniperListener.java index aef0ce931..0c7d26c4c 100644 --- a/favs/src/main/java/com/thevoxelbox/voxelsniper/VoxelSniperListener.java +++ b/favs/src/main/java/com/thevoxelbox/voxelsniper/VoxelSniperListener.java @@ -1,5 +1,6 @@ package com.thevoxelbox.voxelsniper; +import com.boydti.fawe.config.BBC; import com.boydti.fawe.object.FawePlayer; import com.sk89q.minecraft.util.commands.CommandException; import com.sk89q.worldedit.extension.platform.CommandManager; @@ -78,30 +79,38 @@ public class VoxelSniperListener implements Listener } FawePlayer fp = FawePlayer.wrap(player); - ExceptionConverter exceptionConverter = CommandManager.getInstance().getExceptionConverter(); - try { - try { - return found.onCommand(player, split); - } catch (Throwable t) { - Throwable next = t; - exceptionConverter.convert(next); - while (next.getCause() != null) { - next = next.getCause(); - exceptionConverter.convert(next); + if (!fp.runAction(new Runnable() { + @Override + public void run() { + ExceptionConverter exceptionConverter = CommandManager.getInstance().getExceptionConverter(); + try { + try { + found.onCommand(player, split); + return; + } catch (Throwable t) { + Throwable next = t; + exceptionConverter.convert(next); + while (next.getCause() != null) { + next = next.getCause(); + exceptionConverter.convert(next); + } + throw next; + } + } catch (CommandException e) { + String message = e.getMessage(); + if (message != null) { + fp.sendMessage(e.getMessage()); + return; + } + e.printStackTrace(); + } catch (Throwable e) { + e.printStackTrace(); } - throw next; + fp.sendMessage("An unknown FAWE error has occurred! Please see console."); } - } catch (CommandException e) { - String message = e.getMessage(); - if (message != null) { - fp.sendMessage(e.getMessage()); - return true; - } - e.printStackTrace(); - } catch (Throwable e) { - e.printStackTrace(); + }, false, true)) { + BBC.WORLDEDIT_COMMAND_LIMIT.send(fp); } - fp.sendMessage("An unknown FAWE error has occurred! Please see console."); return true; } diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/v1_13_1/Spigot_v1_13_R2.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/v1_13_1/Spigot_v1_13_R2.java index a8be7dc4e..4ca62424e 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/v1_13_1/Spigot_v1_13_R2.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/adapter/v1_13_1/Spigot_v1_13_R2.java @@ -273,7 +273,7 @@ public final class Spigot_v1_13_R2 extends CachedBukkitAdapter implements Bukkit if (existing == blockData) return true; if (section == null) { if (blockData.isAir()) return true; - sections[y4] = new ChunkSection(y4 << 4, nmsWorld.worldProvider.g()); + sections[y4] = section = new ChunkSection(y4 << 4, nmsWorld.worldProvider.g()); } if (existing.e() != blockData.e() || existing.getMaterial().f() != blockData.getMaterial().f()) { nmsChunk.a(pos = new BlockPosition(x, y, z), blockData, false); diff --git a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlock.java b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlock.java index d7edc9330..e13815f15 100644 --- a/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlock.java +++ b/worldedit-bukkit/src/main/java/com/boydti/fawe/bukkit/wrapper/AsyncBlock.java @@ -299,47 +299,48 @@ public class AsyncBlock implements Block { return null; } - public Block getBukkitBlock() { + @Deprecated + private Block getUnsafeBlock() { return world.getBukkitWorld().getBlockAt(x, y, z); } @Override public boolean breakNaturally() { - return TaskManager.IMP.sync(() -> getBukkitBlock().breakNaturally()); + return TaskManager.IMP.sync(() -> getUnsafeBlock().breakNaturally()); } @Override public boolean breakNaturally(ItemStack tool) { - return TaskManager.IMP.sync(() -> getBukkitBlock().breakNaturally(tool)); + return TaskManager.IMP.sync(() -> getUnsafeBlock().breakNaturally(tool)); } @Override public Collection getDrops() { - return TaskManager.IMP.sync(() -> getBukkitBlock().getDrops()); + return TaskManager.IMP.sync(() -> getUnsafeBlock().getDrops()); } @Override public Collection getDrops(ItemStack tool) { - return TaskManager.IMP.sync(() -> getBukkitBlock().getDrops(tool)); + return TaskManager.IMP.sync(() -> getUnsafeBlock().getDrops(tool)); } @Override public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { - this.getBukkitBlock().setMetadata(metadataKey, newMetadataValue); + this.getUnsafeBlock().setMetadata(metadataKey, newMetadataValue); } @Override public List getMetadata(String metadataKey) { - return this.getBukkitBlock().getMetadata(metadataKey); + return this.getUnsafeBlock().getMetadata(metadataKey); } @Override public boolean hasMetadata(String metadataKey) { - return this.getBukkitBlock().hasMetadata(metadataKey); + return this.getUnsafeBlock().hasMetadata(metadataKey); } @Override public void removeMetadata(String metadataKey, Plugin owningPlugin) { - this.getBukkitBlock().removeMetadata(metadataKey, owningPlugin); + this.getUnsafeBlock().removeMetadata(metadataKey, owningPlugin); } } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/jnbt/NBTStreamer.java b/worldedit-core/src/main/java/com/boydti/fawe/jnbt/NBTStreamer.java index e9379b390..15dd8036e 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/jnbt/NBTStreamer.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/jnbt/NBTStreamer.java @@ -4,6 +4,9 @@ import com.boydti.fawe.config.BBC; import com.boydti.fawe.object.RunnableVal2; import com.boydti.fawe.object.exception.FaweException; import com.sk89q.jnbt.NBTInputStream; + +import java.io.DataInput; +import java.io.DataInputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -87,6 +90,8 @@ public class NBTStreamer { public abstract void run(int index, int byteValue); } + public static abstract class LazyReader implements BiConsumer {} + public static abstract class LongReader implements BiConsumer { @Override public void accept(Integer index, Long value) { diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java index b2863776e..c5431a081 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/FaweClipboard.java @@ -103,11 +103,9 @@ public abstract class FaweClipboard { return tiles; } - public void close() { - } + public void close() {} - public void flush() { - } + public void flush() {} /** * Stores entity data. diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/LazyClipboardHolder.java b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/LazyClipboardHolder.java index d16bf31b1..79701662c 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/LazyClipboardHolder.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/LazyClipboardHolder.java @@ -40,13 +40,7 @@ public class LazyClipboardHolder extends URIClipboardHolder { try (InputStream in = source.openBufferedStream()) { final ClipboardReader reader = format.getReader(in); final Clipboard clipboard; - if (reader instanceof SchematicReader) { - this.clipboard = ((SchematicReader) reader).read(uuid); - } else if (reader instanceof StructureFormat) { - this.clipboard = ((StructureFormat) reader).read(uuid); - } else { - this.clipboard = reader.read(); - } + this.clipboard = reader.read(uuid); } } catch (Throwable e) { e.printStackTrace(); diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/ReadOnlyClipboard.java b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/ReadOnlyClipboard.java index 35d4f2553..7792c9652 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/ReadOnlyClipboard.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/ReadOnlyClipboard.java @@ -23,11 +23,11 @@ public abstract class ReadOnlyClipboard extends FaweClipboard { this.region = region; } - public static ReadOnlyClipboard of(final EditSession editSession, final Region region) { + public static ReadOnlyClipboard of(final Extent editSession, final Region region) { return of(editSession, region, true, false); } - public static ReadOnlyClipboard of(final EditSession editSession, final Region region, boolean copyEntities, boolean copyBiomes) { + public static ReadOnlyClipboard of(final Extent editSession, final Region region, boolean copyEntities, boolean copyBiomes) { return new WorldCopyClipboard(editSession, region, copyEntities, copyBiomes); } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/WorldCopyClipboard.java b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/WorldCopyClipboard.java index 66df78869..1c30b0f60 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/WorldCopyClipboard.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/WorldCopyClipboard.java @@ -8,7 +8,7 @@ import com.sk89q.worldedit.EditSession; import com.sk89q.worldedit.MutableBlockVector2D; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEditException; -import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.extent.Extent; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.function.RegionFunction; @@ -17,7 +17,6 @@ import com.sk89q.worldedit.function.visitor.RegionVisitor; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.world.biome.BaseBiome; -import com.sk89q.worldedit.world.block.BlockStateHolder; import java.util.ArrayList; import java.util.List; @@ -29,13 +28,13 @@ public class WorldCopyClipboard extends ReadOnlyClipboard { private final boolean hasBiomes; private final boolean hasEntities; private MutableBlockVector2D mutableBlockVector2D = new MutableBlockVector2D(); - public final EditSession editSession; + public final Extent extent; - public WorldCopyClipboard(EditSession editSession, Region region) { + public WorldCopyClipboard(Extent editSession, Region region) { this(editSession, region, true, false); } - public WorldCopyClipboard(EditSession editSession, Region region, boolean hasEntities, boolean hasBiomes) { + public WorldCopyClipboard(Extent editSession, Region region, boolean hasEntities, boolean hasBiomes) { super(region); this.hasBiomes = hasBiomes; this.hasEntities = hasEntities; @@ -43,27 +42,27 @@ public class WorldCopyClipboard extends ReadOnlyClipboard { this.mx = origin.getBlockX(); this.my = origin.getBlockY(); this.mz = origin.getBlockZ(); - this.editSession = editSession; + this.extent = editSession; } @Override public BlockState getBlock(int x, int y, int z) { - return editSession.getLazyBlock(mx + x, my + y, mz + z); + return extent.getLazyBlock(mx + x, my + y, mz + z); } public BlockState getBlockAbs(int x, int y, int z) { - return editSession.getLazyBlock(x, y, z); + return extent.getLazyBlock(x, y, z); } @Override public BaseBiome getBiome(int x, int z) { - return editSession.getBiome(mutableBlockVector2D.setComponents(mx + x, mz + z)); + return extent.getBiome(mutableBlockVector2D.setComponents(mx + x, mz + z)); } @Override public List getEntities() { if (!hasEntities) return new ArrayList<>(); - return editSession.getEntities(getRegion()); + return extent.getEntities(getRegion()); } @Override @@ -96,7 +95,7 @@ public class WorldCopyClipboard extends ReadOnlyClipboard { task.run(x, y, z, block); return true; } - }, editSession); + }, extent instanceof EditSession ? (EditSession) extent : null); Operations.completeBlindly(visitor); } else { CuboidRegion cuboidEquivalent = new CuboidRegion(region.getMinimumPoint(), region.getMaximumPoint()); @@ -124,7 +123,7 @@ public class WorldCopyClipboard extends ReadOnlyClipboard { } return true; } - }, editSession); + }, extent instanceof EditSession ? (EditSession) extent : null); Operations.completeBlindly(visitor); } } else { diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/WorldCutClipboard.java b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/WorldCutClipboard.java index f4e3740f0..f77c2bf65 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/WorldCutClipboard.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/clipboard/WorldCutClipboard.java @@ -1,10 +1,8 @@ package com.boydti.fawe.object.clipboard; import com.sk89q.worldedit.EditSession; -import com.sk89q.worldedit.blocks.BaseBlock; import com.sk89q.worldedit.world.block.BlockState; import com.sk89q.worldedit.regions.Region; -import com.sk89q.worldedit.world.block.BlockStateHolder; public class WorldCutClipboard extends WorldCopyClipboard { public WorldCutClipboard(EditSession editSession, Region region, boolean copyEntities, boolean copyBiome) { @@ -20,20 +18,24 @@ public class WorldCutClipboard extends WorldCopyClipboard { int xx = mx + x; int yy = my + y; int zz = mz + z; - BlockState block = editSession.getLazyBlock(xx, yy, zz); - editSession.setBlock(xx, yy, zz, EditSession.nullBlock); + BlockState block = extent.getLazyBlock(xx, yy, zz); + extent.setBlock(xx, yy, zz, EditSession.nullBlock); return block; } public BlockState getBlockAbs(int x, int y, int z) { - BlockState block = editSession.getLazyBlock(x, y, z); - editSession.setBlock(x, y, z, EditSession.nullBlock); + BlockState block = extent.getLazyBlock(x, y, z); + extent.setBlock(x, y, z, EditSession.nullBlock); return block; } @Override public void forEach(BlockReader task, boolean air) { super.forEach(task, air); - editSession.flushQueue(); + if (extent instanceof EditSession) { + ((EditSession) extent).flushQueue(); + } else { + extent.commit(); + } } } \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/boydti/fawe/util/IOUtil.java b/worldedit-core/src/main/java/com/boydti/fawe/util/IOUtil.java index 3ad0a8d4d..25236077a 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/util/IOUtil.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/util/IOUtil.java @@ -34,7 +34,7 @@ public final class IOUtil { out.write((v >>> 0) & 0xFF); } - public static void writeVarInt(OutputStream out, int i) throws IOException { + public static final void writeVarInt(final OutputStream out, int i) throws IOException { while((i & -128) != 0) { out.write(i & 127 | 128); i >>>= 7; @@ -42,7 +42,7 @@ public final class IOUtil { out.write(i); } - public static int readVarInt(InputStream in) throws IOException { + public static final int readVarInt(InputStream in) throws IOException { int i = 0; int offset = 0; int b; @@ -53,4 +53,39 @@ public final class IOUtil { i |= b << offset; return i; } + + public static final void copy(InputStream in, OutputStream out) throws IOException { + byte[] buf = new byte[8192]; + while (true) { + int r = in.read(buf); + if (r == -1) { + break; + } + out.write(buf, 0, r); + } + } + + public static final int copy(InputStream in, OutputStream out, int len) throws IOException { + byte[] buf = new byte[8192]; + while (len > 0) { + int r = in.read(buf, 0, Math.min(buf.length, len)); + if (r == -1) { + break; + } + len -= r; + out.write(buf, 0, r); + } + return len; + } + + public static final void copy(InputStream in, DataOutput out) throws IOException { + byte[] buf = new byte[8192]; + while (true) { + int r = in.read(buf); + if (r == -1) { + break; + } + out.write(buf, 0, r); + } + } } diff --git a/worldedit-core/src/main/java/com/sk89q/jnbt/NBTInputStream.java b/worldedit-core/src/main/java/com/sk89q/jnbt/NBTInputStream.java index c4fdf10ec..86ffde60c 100644 --- a/worldedit-core/src/main/java/com/sk89q/jnbt/NBTInputStream.java +++ b/worldedit-core/src/main/java/com/sk89q/jnbt/NBTInputStream.java @@ -44,7 +44,7 @@ import java.util.function.Function; */ public final class NBTInputStream implements Closeable { - private final DataInput is; + private final DataInputStream is; /** * Creates a new {@code NBTInputStream}, which will source its data @@ -61,8 +61,8 @@ public final class NBTInputStream implements Closeable { this.is = dis; } - public NBTInputStream(DataInput di) { - this.is = di; + public DataInputStream getInputStream() { + return is; } /** @@ -178,7 +178,7 @@ public final class NBTInputStream implements Closeable { NBTStreamer.ByteReader byteReader = (NBTStreamer.ByteReader) reader; int i = 0; if (is instanceof InputStream) { - DataInputStream dis = (DataInputStream) is; + DataInputStream dis = is; if (length > 1024) { if (buf == null) { buf = new byte[1024]; @@ -211,6 +211,8 @@ public final class NBTInputStream implements Closeable { byteReader.run(i, is.readByte() & 0xFF); } } + } else if (reader instanceof NBTStreamer.LazyReader) { + reader.accept(length, is); } else { for (int i = 0; i < length; i++) { reader.accept(i, is.readByte()); @@ -273,6 +275,8 @@ public final class NBTInputStream implements Closeable { for (int i = 0; i < length; i++) { byteReader.run(i, is.readInt()); } + } else if (reader instanceof NBTStreamer.LazyReader) { + reader.accept(length, is); } else { for (int i = 0; i < length; i++) { reader.accept(i, is.readInt()); @@ -296,6 +300,8 @@ public final class NBTInputStream implements Closeable { for (int i = 0; i < length; i++) { longReader.run(i, is.readLong()); } + } else if (reader instanceof NBTStreamer.LazyReader) { + reader.accept(length, is); } else { for (int i = 0; i < length; i++) { reader.accept(i, is.readLong()); diff --git a/worldedit-core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java b/worldedit-core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java index 3ba992ac0..a9bf7e2b4 100644 --- a/worldedit-core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java +++ b/worldedit-core/src/main/java/com/sk89q/jnbt/NBTOutputStream.java @@ -201,7 +201,7 @@ public final class NBTOutputStream implements Closeable { * @param tag The tag. * @throws IOException if an I/O error occurs. */ - private void writeTagPayload(Tag tag) throws IOException { + public void writeTagPayload(Tag tag) throws IOException { int type = NBTUtils.getTypeCode(tag.getClass()); switch (type) { case NBTConstants.TYPE_END: @@ -408,6 +408,4 @@ public final class NBTOutputStream implements Closeable { public void flush() throws IOException { if (os instanceof Flushable) ((Flushable) os).flush(); } - - } \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java index 7f739eeb0..54e1b8979 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/command/SchematicCommands.java @@ -294,7 +294,7 @@ public class SchematicCommands extends MethodCommands { @Command(aliases = {"save"}, usage = "[format] ", desc = "Save a schematic into your clipboard") @Deprecated @CommandPermissions({"worldedit.clipboard.save", "worldedit.schematic.save", "worldedit.schematic.save.other"}) - public void save(final Player player, final LocalSession session, @Optional("nbt") final String formatName, String filename, @Switch('g') boolean global) throws CommandException, WorldEditException { + public void save(final Player player, final LocalSession session, @Optional("schem") final String formatName, String filename, @Switch('g') boolean global) throws CommandException, WorldEditException { final LocalConfiguration config = this.worldEdit.getConfiguration(); final ClipboardFormat format = ClipboardFormat.findByAlias(formatName); if (format == null) { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java index 811772868..c81716677 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/Extent.java @@ -21,8 +21,10 @@ package com.sk89q.worldedit.extent; import com.boydti.fawe.jnbt.anvil.generator.*; import com.boydti.fawe.object.PseudoRandom; +import com.boydti.fawe.object.clipboard.WorldCopyClipboard; import com.sk89q.worldedit.*; import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.util.Countable; import com.sk89q.worldedit.world.block.*; @@ -359,6 +361,19 @@ public interface Extent extends InputExtent, OutputExtent { return distribution; } + /** + * Lazily copy a region + * + * @param region + * @return + */ + default BlockArrayClipboard lazyCopy(Region region) { + WorldCopyClipboard faweClipboard = new WorldCopyClipboard(this, region); + BlockArrayClipboard weClipboard = new BlockArrayClipboard(region, faweClipboard); + weClipboard.setOrigin(region.getMinimumPoint()); + return weClipboard; + } + @Nullable @Override default Operation commit() { diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java index 475ee1cbc..e73429309 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardFormat.java @@ -94,6 +94,7 @@ public enum ClipboardFormat { @Override public boolean isFormat(File file) { + if (!file.getName().toLowerCase().endsWith(".schematic")) return false; DataInputStream str = null; try { str = new DataInputStream(new GZIPInputStream(new FileInputStream(file))); @@ -151,6 +152,7 @@ public enum ClipboardFormat { @Override public boolean isFormat(File file) { + if (!file.getName().toLowerCase().endsWith(".schem")) return false; DataInputStream str = null; try { str = new DataInputStream(new GZIPInputStream(new FileInputStream(file))); @@ -496,13 +498,7 @@ public enum ClipboardFormat { LocalSession session = WorldEdit.getInstance().getSessionManager().get(player); session.setClipboard(null); - if (reader instanceof SchematicReader) { - clipboard = ((SchematicReader) reader).read(player.getUniqueId()); - } else if (reader instanceof StructureFormat) { - clipboard = ((StructureFormat) reader).read(player.getUniqueId()); - } else { - clipboard = reader.read(); - } + clipboard = reader.read(player.getUniqueId()); URIClipboardHolder holder = new URIClipboardHolder(uri, clipboard); session.setClipboard(holder); return holder; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardReader.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardReader.java index 1448ca123..c248a3ad8 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardReader.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/ClipboardReader.java @@ -24,6 +24,7 @@ import com.sk89q.worldedit.world.registry.Registries; import java.io.Closeable; import java.io.IOException; +import java.util.UUID; /** * Reads {@code Clipboard}s. @@ -40,4 +41,7 @@ public interface ClipboardReader extends Closeable { */ Clipboard read() throws IOException; -} + default Clipboard read(UUID uuid) throws IOException { + return read(); + } +} \ No newline at end of file diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java index f6ceb6e07..4c54af4e8 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicReader.java @@ -19,6 +19,18 @@ package com.sk89q.worldedit.extent.clipboard.io; +import com.boydti.fawe.Fawe; +import com.boydti.fawe.config.Settings; +import com.boydti.fawe.jnbt.NBTStreamer; +import com.boydti.fawe.object.FaweInputStream; +import com.boydti.fawe.object.FaweOutputStream; +import com.boydti.fawe.object.clipboard.CPUOptimizedClipboard; +import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard; +import com.boydti.fawe.object.clipboard.FaweClipboard; +import com.boydti.fawe.object.clipboard.MemoryOptimizedClipboard; +import com.boydti.fawe.object.io.FastByteArrayOutputStream; +import com.boydti.fawe.object.io.FastByteArraysInputStream; +import com.boydti.fawe.util.IOUtil; import com.google.common.collect.Maps; import com.sk89q.jnbt.ByteArrayTag; import com.sk89q.jnbt.CompoundTag; @@ -28,11 +40,13 @@ import com.sk89q.jnbt.ListTag; import com.sk89q.jnbt.NBTInputStream; import com.sk89q.jnbt.NamedTag; import com.sk89q.jnbt.ShortTag; +import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.BlockVector; import com.sk89q.worldedit.Vector; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.blocks.BaseBlock; +import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extension.input.ParserContext; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; @@ -40,12 +54,21 @@ import com.sk89q.worldedit.extent.clipboard.io.legacycompat.NBTCompatibilityHand import com.sk89q.worldedit.regions.CuboidRegion; import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; +import com.sk89q.worldedit.world.entity.EntityType; +import com.sk89q.worldedit.world.entity.EntityTypes; +import net.jpountz.lz4.LZ4BlockInputStream; +import net.jpountz.lz4.LZ4BlockOutputStream; +import java.io.DataInputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; +import java.util.function.BiConsumer; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -77,141 +100,151 @@ public class SpongeSchematicReader extends NBTSchematicReader { @Override public Clipboard read() throws IOException { - NamedTag rootTag = inputStream.readNamedTag(); - if (!rootTag.getName().equals("Schematic")) { - throw new IOException("Tag 'Schematic' does not exist or is not first"); - } - CompoundTag schematicTag = (CompoundTag) rootTag.getTag(); + return read(UUID.randomUUID()); + } - // Check - Map schematic = schematicTag.getValue(); - int version = requireTag(schematic, "Version", IntTag.class).getValue(); - switch (version) { - case 1: - return readVersion1(schematic); - default: - throw new IOException("This schematic version is currently not supported"); + @Override + public Clipboard read(UUID uuid) throws IOException { + return readVersion1(uuid); + } + + private int width, height, length; + private int offsetX, offsetY, offsetZ; + private char[] palette; + private Vector min; + private FaweClipboard fc; + + private FaweClipboard setupClipboard(int size, UUID uuid) { + if (fc != null) { + if (fc.getDimensions().getX() == 0) { + fc.setDimensions(new Vector(size, 1, 1)); + } + return fc; + } + if (Settings.IMP.CLIPBOARD.USE_DISK) { + return fc = new DiskOptimizedClipboard(size, 1, 1, uuid); + } else if (Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL == 0) { + return fc = new CPUOptimizedClipboard(size, 1, 1); + } else { + return fc = new MemoryOptimizedClipboard(size, 1, 1); } } - private Clipboard readVersion1(Map schematic) throws IOException { - Vector origin; - Region region; + private Clipboard readVersion1(UUID uuid) throws IOException { + width = height = length = offsetX = offsetY = offsetZ = Integer.MIN_VALUE; - Map metadata = requireTag(schematic, "Metadata", CompoundTag.class).getValue(); + final BlockArrayClipboard clipboard = new BlockArrayClipboard(new CuboidRegion(new Vector(0, 0, 0), new Vector(0, 0, 0)), fc); + FastByteArrayOutputStream blocksOut = new FastByteArrayOutputStream(); + FastByteArrayOutputStream biomesOut = new FastByteArrayOutputStream(); - int width = requireTag(schematic, "Width", ShortTag.class).getValue(); - int height = requireTag(schematic, "Height", ShortTag.class).getValue(); - int length = requireTag(schematic, "Length", ShortTag.class).getValue(); - - int[] offsetParts = requireTag(schematic, "Offset", IntArrayTag.class).getValue(); - if (offsetParts.length != 3) { - throw new IOException("Invalid offset specified in schematic."); - } - - Vector min = new Vector(offsetParts[0], offsetParts[1], offsetParts[2]); - - if (metadata.containsKey("WEOffsetX")) { - // We appear to have WorldEdit Metadata - int offsetX = requireTag(metadata, "WEOffsetX", IntTag.class).getValue(); - int offsetY = requireTag(metadata, "WEOffsetY", IntTag.class).getValue(); - int offsetZ = requireTag(metadata, "WEOffsetZ", IntTag.class).getValue(); - Vector offset = new Vector(offsetX, offsetY, offsetZ); - origin = min.subtract(offset); + NBTStreamer streamer = new NBTStreamer(inputStream); + streamer.addReader("Schematic.Width", (BiConsumer) (i, v) -> width = v); + streamer.addReader("Schematic.Height", (BiConsumer) (i, v) -> height = v); + streamer.addReader("Schematic.Length", (BiConsumer) (i, v) -> length = v); + streamer.addReader("Schematic.Offset", (BiConsumer) (i, v) -> min = new BlockVector(v[0], v[1], v[2])); + streamer.addReader("Schematic.Metadata.WEOffsetX", (BiConsumer) (i, v) -> offsetX = v); + streamer.addReader("Schematic.Metadata.WEOffsetY", (BiConsumer) (i, v) -> offsetY = v); + streamer.addReader("Schematic.Metadata.WEOffsetZ", (BiConsumer) (i, v) -> offsetZ = v); + streamer.addReader("Schematic.Palette", (BiConsumer>) (i, v) -> { + palette = new char[v.size()]; + for (Map.Entry entry : v.entrySet()) { + BlockState state = BlockState.get(entry.getKey()); + int index = ((IntTag) entry.getValue()).getValue(); + palette[index] = (char) state.getOrdinal(); + } + }); + streamer.addReader("Schematic.BlockData.#", new NBTStreamer.LazyReader() { + @Override + public void accept(Integer arrayLen, DataInputStream dis) { + try (FaweOutputStream blocks = new FaweOutputStream(new LZ4BlockOutputStream(blocksOut))) { + IOUtil.copy(dis, blocks, arrayLen); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + streamer.addReader("Schematic.Biomes.#", new NBTStreamer.LazyReader() { + @Override + public void accept(Integer arrayLen, DataInputStream dis) { + try (FaweOutputStream biomes = new FaweOutputStream(new LZ4BlockOutputStream(biomesOut))) { + IOUtil.copy(dis, biomes, arrayLen); + } catch (IOException e) { + e.printStackTrace(); + } + } + }); + streamer.addReader("Schematic.TileEntities.#", new BiConsumer() { + @Override + public void accept(Integer index, CompoundTag value) { + if (fc == null) { + setupClipboard(0, uuid); + } + int[] pos = value.getIntArray("Pos"); + int x = pos[0]; + int y = pos[1]; + int z = pos[2]; + fc.setTile(x, y, z, value); + } + }); + streamer.addReader("Schematic.Entities.#", new BiConsumer() { + @Override + public void accept(Integer index, CompoundTag compound) { + if (fc == null) { + setupClipboard(0, uuid); + } + String id = compound.getString("id"); + if (id.isEmpty()) { + return; + } + ListTag positionTag = compound.getListTag("Pos"); + ListTag directionTag = compound.getListTag("Rotation"); + EntityType type = EntityTypes.parse(id); + if (type != null) { + compound.getValue().put("Id", new StringTag(type.getId())); + BaseEntity state = new BaseEntity(type, compound); + fc.createEntity(clipboard, positionTag.asDouble(0), positionTag.asDouble(1), positionTag.asDouble(2), (float) directionTag.asDouble(0), (float) directionTag.asDouble(1), state); + } else { + Fawe.debug("Invalid entity: " + id); + } + } + }); + streamer.readFully(); + if (fc == null) setupClipboard(length * width * height, uuid); + Vector origin = min; + CuboidRegion region; + if (offsetX != Integer.MIN_VALUE && offsetY != Integer.MIN_VALUE && offsetZ != Integer.MIN_VALUE) { + origin = origin.subtract(new Vector(offsetX, offsetY, offsetZ)); region = new CuboidRegion(min, min.add(width, height, length).subtract(Vector.ONE)); } else { - origin = min; - region = new CuboidRegion(origin, origin.add(width, height, length).subtract(Vector.ONE)); + region = new CuboidRegion(min, min.add(width, height, length).subtract(Vector.ONE)); } - - int paletteMax = requireTag(schematic, "PaletteMax", IntTag.class).getValue(); - Map paletteObject = requireTag(schematic, "Palette", CompoundTag.class).getValue(); - if (paletteObject.size() != paletteMax) { - throw new IOException("Differing given palette size to actual size"); - } - - Map palette = new HashMap<>(); - - ParserContext parserContext = new ParserContext(); - parserContext.setRestricted(false); - parserContext.setTryLegacy(false); - parserContext.setPreferringWildcard(false); - - for (String palettePart : paletteObject.keySet()) { - int id = requireTag(paletteObject, palettePart, IntTag.class).getValue(); - BlockState state = BlockState.get(palettePart); - palette.put(id, state); - } - - byte[] blocks = requireTag(schematic, "BlockData", ByteArrayTag.class).getValue(); - - Map> tileEntitiesMap = new HashMap<>(); - try { - List> tileEntityTags = ((ListTag) requireTag(schematic, "TileEntities", ListTag.class)).getValue().stream() - .map(CompoundTag::getValue) - .collect(Collectors.toList()); - - for (Map tileEntity : tileEntityTags) { - int[] pos = requireTag(tileEntity, "Pos", IntArrayTag.class).getValue(); - tileEntitiesMap.put(new BlockVector(pos[0], pos[1], pos[2]), tileEntity); - } - } catch (Exception e) { - throw new IOException("Failed to load Tile Entities: " + e.getMessage()); - } - - BlockArrayClipboard clipboard = new BlockArrayClipboard(region); - clipboard.setOrigin(origin); - - int index = 0; - int i = 0; - int value = 0; - int varintLength = 0; - while (i < blocks.length) { - value = 0; - varintLength = 0; - - while (true) { - value |= (blocks[i] & 127) << (varintLength++ * 7); - if (varintLength > 5) { - throw new RuntimeException("VarInt too big (probably corrupted data)"); - } - if ((blocks[i] & 128) != 128) { - i++; - break; - } - i++; - } - // index = (y * length + z) * width + x - int y = index / (width * length); - int z = (index % (width * length)) / width; - int x = (index % (width * length)) % width; - BlockState state = palette.get(value); - BlockVector pt = new BlockVector(x, y, z); - try { - if (state.getBlockType().getMaterial().hasContainer() && tileEntitiesMap.containsKey(pt)) { - Map values = Maps.newHashMap(tileEntitiesMap.get(pt)); - for (NBTCompatibilityHandler handler : COMPATIBILITY_HANDLERS) { - if (handler.isAffectedBlock(state)) { - handler.updateNBT(state, values); - } + if (blocksOut.getSize() != 0) { + try (FaweInputStream fis = new FaweInputStream(new LZ4BlockInputStream(new FastByteArraysInputStream(blocksOut.toByteArrays())))) { + int volume = width * height * length; + if (palette.length < 128) { + for (int index = 0; index < volume; index++) { + BlockState state = BlockTypes.states[palette[fis.read()]]; + fc.setBlock(index, state); } - values.put("x", new IntTag(pt.getBlockX())); - values.put("y", new IntTag(pt.getBlockY())); - values.put("z", new IntTag(pt.getBlockZ())); - values.put("id", values.get("Id")); - values.remove("Id"); - values.remove("Pos"); - clipboard.setBlock(clipboard.getMinimumPoint().add(pt), new BaseBlock(state, new CompoundTag(values))); } else { - clipboard.setBlock(clipboard.getMinimumPoint().add(pt), state); + for (int index = 0; index < volume; index++) { + BlockState state = BlockTypes.states[palette[fis.readVarInt()]]; + fc.setBlock(index, state); + } } - } catch (WorldEditException e) { - throw new IOException("Failed to load a block in the schematic"); } - - index++; } - + if (biomesOut.getSize() != 0) { + try (FaweInputStream fis = new FaweInputStream(new LZ4BlockInputStream(new FastByteArraysInputStream(biomesOut.toByteArrays())))) { + int volume = width * length; + for (int index = 0; index < volume; index++) { + fc.setBiome(index, fis.read()); + } + } + } + fc.setDimensions(new Vector(width, height, length)); + clipboard.init(region, fc); + clipboard.setOrigin(origin); return clipboard; } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicWriter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicWriter.java index fdb384e3b..9956b9dbe 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicWriter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SpongeSchematicWriter.java @@ -19,32 +19,36 @@ package com.sk89q.worldedit.extent.clipboard.io; -import static com.google.common.base.Preconditions.checkNotNull; - -import com.sk89q.jnbt.ByteArrayTag; +import com.boydti.fawe.object.clipboard.FaweClipboard; +import com.boydti.fawe.util.IOUtil; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.IntArrayTag; -import com.sk89q.jnbt.IntTag; -import com.sk89q.jnbt.ListTag; +import com.sk89q.jnbt.NBTConstants; import com.sk89q.jnbt.NBTOutputStream; -import com.sk89q.jnbt.ShortTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.BlockVector; import com.sk89q.worldedit.Vector; -import com.sk89q.worldedit.blocks.BaseBlock; -import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.regions.Region; -import com.sk89q.worldedit.world.block.BlockStateHolder; +import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; +import net.jpountz.lz4.LZ4BlockInputStream; +import net.jpountz.lz4.LZ4BlockOutputStream; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Arrays; import java.util.List; import java.util.Map; +import static com.google.common.base.Preconditions.checkNotNull; + /** * Writes schematic files using the Sponge schematic format. */ @@ -65,26 +69,18 @@ public class SpongeSchematicWriter implements ClipboardWriter { @Override public void write(Clipboard clipboard) throws IOException { - // For now always write the latest version. Maybe provide support for earlier if more appear. - outputStream.writeNamedTag("Schematic", new CompoundTag(write1(clipboard))); + write1(clipboard); } - /** - * Writes a version 1 schematic file. - * - * @param clipboard The clipboard - * @return The schematic map - * @throws IOException If an error occurs - */ - private Map write1(Clipboard clipboard) throws IOException { + public void write1(Clipboard clipboard) throws IOException { + // metadata Region region = clipboard.getRegion(); Vector origin = clipboard.getOrigin(); - Vector min = region.getMinimumPoint(); + BlockVector min = region.getMinimumPoint().toBlockVector(); Vector offset = min.subtract(origin); int width = region.getWidth(); int height = region.getHeight(); int length = region.getLength(); - if (width > MAX_SIZE) { throw new IllegalArgumentException("Width of region too large for a .schematic"); } @@ -94,95 +90,118 @@ public class SpongeSchematicWriter implements ClipboardWriter { if (length > MAX_SIZE) { throw new IllegalArgumentException("Length of region too large for a .schematic"); } + // output + final DataOutput rawStream = outputStream.getOutputStream(); + outputStream.writeLazyCompoundTag("Schematic", out -> { + out.writeNamedTag("Version", 1); + out.writeNamedTag("Width", (short) width); + out.writeNamedTag("Height", (short) height); + out.writeNamedTag("Length", (short) length); + out.writeNamedTag("Offset", new int[]{ + min.getBlockX(), + min.getBlockY(), + min.getBlockZ(), + }); - Map schematic = new HashMap<>(); - schematic.put("Version", new IntTag(1)); + out.writeLazyCompoundTag("Metadata", out1 -> { + out1.writeNamedTag("WEOffsetX", offset.getBlockX()); + out1.writeNamedTag("WEOffsetY", offset.getBlockY()); + out1.writeNamedTag("WEOffsetZ", offset.getBlockZ()); + }); - Map metadata = new HashMap<>(); - metadata.put("WEOffsetX", new IntTag(offset.getBlockX())); - metadata.put("WEOffsetY", new IntTag(offset.getBlockY())); - metadata.put("WEOffsetZ", new IntTag(offset.getBlockZ())); + ByteArrayOutputStream blocksCompressed = new ByteArrayOutputStream(); + DataOutputStream blocksOut = new DataOutputStream(new LZ4BlockOutputStream(blocksCompressed)); - schematic.put("Metadata", new CompoundTag(metadata)); + ByteArrayOutputStream tilesCompressed = new ByteArrayOutputStream(); + NBTOutputStream tilesOut = new NBTOutputStream(new LZ4BlockOutputStream(tilesCompressed)); + int[] numTiles = {0}; - schematic.put("Width", new ShortTag((short) width)); - schematic.put("Height", new ShortTag((short) height)); - schematic.put("Length", new ShortTag((short) length)); + List paletteList = new ArrayList<>(); + char[] palette = new char[BlockTypes.states.length]; + Arrays.fill(palette, Character.MAX_VALUE); + int[] paletteMax = {0}; - // The Sponge format Offset refers to the 'min' points location in the world. That's our 'Origin' - schematic.put("Offset", new IntArrayTag(new int[]{ - min.getBlockX(), - min.getBlockY(), - min.getBlockZ(), - })); - int paletteMax = 0; - Map palette = new HashMap<>(); - - List tileEntities = new ArrayList<>(); - - ByteArrayOutputStream buffer = new ByteArrayOutputStream(width * height * length); - - for (int y = 0; y < height; y++) { - int y0 = min.getBlockY() + y; - for (int z = 0; z < length; z++) { - int z0 = min.getBlockZ() + z; - for (int x = 0; x < width; x++) { - int x0 = min.getBlockX() + x; - BlockVector point = new BlockVector(x0, y0, z0); - BlockStateHolder block = clipboard.getFullBlock(point); - if (block.getNbtData() != null) { - Map values = new HashMap<>(); - for (Map.Entry entry : block.getNbtData().getValue().entrySet()) { - values.put(entry.getKey(), entry.getValue()); + FaweClipboard.BlockReader reader = new FaweClipboard.BlockReader() { + @Override + public void run(int x, int y, int z, BlockState block) { + try { + CompoundTag tile = block.getNbtData(); + if (tile != null) { + Map values = tile.getValue(); + values.remove("id"); // Remove 'id' if it exists. We want 'Id' + // Positions are kept in NBT, we don't want that. + values.remove("x"); + values.remove("y"); + values.remove("z"); + if (!values.containsKey("Id")) values.put("Id", new StringTag(block.getNbtId())); + values.put("Pos", new IntArrayTag(new int[]{ + x, + y, + z + })); + numTiles[0]++; + tilesOut.writeTagPayload(tile); } - - values.remove("id"); // Remove 'id' if it exists. We want 'Id' - - // Positions are kept in NBT, we don't want that. - values.remove("x"); - values.remove("y"); - values.remove("z"); - - values.put("Id", new StringTag(block.getNbtId())); - values.put("Pos", new IntArrayTag(new int[]{ - x, - y, - z - })); - - tileEntities.add(new CompoundTag(values)); + int ordinal = block.getOrdinal(); + char value = palette[ordinal]; + if (value == Character.MAX_VALUE) { + int size = paletteMax[0]++; + palette[ordinal] = value = (char) size; + paletteList.add(ordinal); + } + while ((value & -128) != 0) { + blocksOut.write(value & 127 | 128); + value >>>= 7; + } + blocksOut.write(value); + } catch (IOException e) { + throw new RuntimeException(e); } - - String blockKey = block.toImmutableState().getAsString(); - int blockId; - if (palette.containsKey(blockKey)) { - blockId = palette.get(blockKey); - } else { - blockId = paletteMax; - palette.put(blockKey, blockId); - paletteMax++; - } - - while ((blockId & -128) != 0) { - buffer.write(blockId & 127 | 128); - blockId >>>= 7; - } - buffer.write(blockId); + } + }; + if (clipboard instanceof BlockArrayClipboard) { + ((BlockArrayClipboard) clipboard).IMP.forEach(reader, true); + } else { + for (Vector pt : region) { + BlockState block = clipboard.getBlock(pt); + int x = pt.getBlockX() - min.getBlockX(); + int y = pt.getBlockY() - min.getBlockY(); + int z = pt.getBlockZ() - min.getBlockY(); + reader.run(x, y, z, block); } } - } - - schematic.put("PaletteMax", new IntTag(paletteMax)); - - Map paletteTag = new HashMap<>(); - palette.forEach((key, value) -> paletteTag.put(key, new IntTag(value))); - - schematic.put("Palette", new CompoundTag(paletteTag)); - schematic.put("BlockData", new ByteArrayTag(buffer.toByteArray())); - schematic.put("TileEntities", new ListTag(CompoundTag.class, tileEntities)); - - return schematic; + // close + tilesOut.close(); + blocksOut.close(); + // palette max + out.writeNamedTag("PaletteMax", paletteMax[0]); + // palette + out.writeLazyCompoundTag("Palette", out12 -> { + for (int i = 0; i < paletteList.size(); i++) { + int stateOrdinal = paletteList.get(i); + BlockState state = BlockTypes.states[stateOrdinal]; + out12.writeNamedTag(state.getAsString(), i); + } + }); + // Block data + out.writeNamedTagName("BlockData", NBTConstants.TYPE_BYTE_ARRAY); + rawStream.writeInt(blocksOut.size()); + try (LZ4BlockInputStream in = new LZ4BlockInputStream(new ByteArrayInputStream(blocksCompressed.toByteArray()))) { + IOUtil.copy(in, rawStream); + } + // tiles + if (numTiles[0] != 0) { + out.writeNamedTagName("TileEntities", NBTConstants.TYPE_LIST); + rawStream.write(NBTConstants.TYPE_COMPOUND); + rawStream.writeInt(numTiles[0]); + try (LZ4BlockInputStream in = new LZ4BlockInputStream(new ByteArrayInputStream(tilesCompressed.toByteArray()))) { + IOUtil.copy(in, rawStream); + } + } else { + out.writeNamedEmptyList("TileEntities"); + } + }); } @Override