From fbfe3221d7d22c51bfceabf21c657fbc98f54946 Mon Sep 17 00:00:00 2001 From: dordsor21 Date: Fri, 1 Jan 2021 15:01:35 +0000 Subject: [PATCH] Fix fast schematic reader/writer - Have both sponge and fast r/w available but default to "fast" - Correctly "offset" entities in the schematic, and add a legacy mode for loading old FAWE schematics with entities required. - Lazily reading means it's read in order of appearance in the inputstream so we need to read schematic version first (skip past everything) and then reset the stream. Fixes #740 - Add an FAWEVersion to the metadata - Correctly actually return a BlockArrayClipboard when required. Fixes #454 --- .../fawe/jnbt/streamer/StreamDelegate.java | 2 +- .../java/com/sk89q/jnbt/NBTInputStream.java | 26 +++-- .../worldedit/command/SchematicCommands.java | 6 +- .../clipboard/io/BuiltInClipboardFormat.java | 94 ++++++++++++++++++- .../clipboard/io/FastSchematicReader.java | 65 ++++++++++--- .../clipboard/io/FastSchematicWriter.java | 41 +++----- .../extent/clipboard/io/SchematicReader.java | 2 +- .../clipboard/io/SpongeSchematicWriter.java | 2 + 8 files changed, 181 insertions(+), 57 deletions(-) diff --git a/worldedit-core/src/main/java/com/boydti/fawe/jnbt/streamer/StreamDelegate.java b/worldedit-core/src/main/java/com/boydti/fawe/jnbt/streamer/StreamDelegate.java index a404c8956..eea8b8545 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/jnbt/streamer/StreamDelegate.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/jnbt/streamer/StreamDelegate.java @@ -200,7 +200,7 @@ public class StreamDelegate { Object raw = is.readTagPayloadRaw(type, depth); valueReader.apply(0, raw); } else { - is.readTagPaylodLazy(type, depth + 1, this); + is.readTagPayloadLazy(type, depth + 1, this); } } 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 c2282e9fc..c86179b4b 100644 --- a/worldedit-core/src/main/java/com/sk89q/jnbt/NBTInputStream.java +++ b/worldedit-core/src/main/java/com/sk89q/jnbt/NBTInputStream.java @@ -61,6 +61,14 @@ public final class NBTInputStream implements Closeable { this.is = dis; } + public void mark(int mark) { + is.mark(mark); + } + + public void reset() throws IOException { + is.reset(); + } + /** * Reads an NBT tag from the stream. * @@ -99,7 +107,7 @@ public final class NBTInputStream implements Closeable { if (child != null) { child.acceptRoot(this, type, 0); } else { - readTagPaylodLazy(type, 0); + readTagPayloadLazy(type, 0); } } catch (Throwable e) { e.printStackTrace(); @@ -119,7 +127,7 @@ public final class NBTInputStream implements Closeable { private byte[] buf; - public void readTagPaylodLazy(int type, int depth) throws IOException { + public void readTagPayloadLazy(int type, int depth) throws IOException { switch (type) { case NBTConstants.TYPE_END: return; @@ -152,7 +160,7 @@ public final class NBTInputStream implements Closeable { int childType = is.readByte(); length = is.readInt(); for (int i = 0; i < length; ++i) { - readTagPaylodLazy(childType, depth + 1); + readTagPayloadLazy(childType, depth + 1); } return; } @@ -165,7 +173,7 @@ public final class NBTInputStream implements Closeable { return; } is.skipBytes(is.readShort() & 0xFFFF); - readTagPaylodLazy(childType, depth + 1); + readTagPayloadLazy(childType, depth + 1); } } case NBTConstants.TYPE_INT_ARRAY: { @@ -181,7 +189,7 @@ public final class NBTInputStream implements Closeable { } } - public void readTagPaylodLazy(int type, int depth, StreamDelegate scope) throws IOException { + public void readTagPayloadLazy(int type, int depth, StreamDelegate scope) throws IOException { switch (type) { case NBTConstants.TYPE_END: return; @@ -293,11 +301,11 @@ public final class NBTInputStream implements Closeable { child = scope.get0(); if (child == null) { for (int i = 0; i < length; ++i) { - readTagPaylodLazy(childType, depth + 1); + readTagPayloadLazy(childType, depth + 1); } } else { for (int i = 0; i < length; ++i) { - readTagPaylodLazy(childType, depth + 1, child); + readTagPayloadLazy(childType, depth + 1, child); } } return; @@ -330,9 +338,9 @@ public final class NBTInputStream implements Closeable { } StreamDelegate child = scope.get(is); if (child == null) { - readTagPaylodLazy(childType, depth + 1); + readTagPayloadLazy(childType, depth + 1); } else { - readTagPaylodLazy(childType, depth + 1, child); + readTagPayloadLazy(childType, depth + 1, child); } } } 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 037cae63c..cf3488921 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 @@ -147,7 +147,7 @@ public class SchematicCommands { @Deprecated @CommandPermissions({"worldedit.clipboard.load", "worldedit.schematic.load", "worldedit.schematic.load.web", "worldedit.schematic.load.asset"}) public void loadall(Player player, LocalSession session, - @Arg(desc = "Format name.", def = "schematic") + @Arg(desc = "Format name.", def = "fast") String formatName, @Arg(desc = "File name.") String filename, @@ -223,7 +223,7 @@ public class SchematicCommands { public void load(Actor actor, LocalSession session, @Arg(desc = "File name.") String filename, - @Arg(desc = "Format name.", def = "sponge") + @Arg(desc = "Format name.", def = "fast") String formatName) throws FilenameException { LocalConfiguration config = worldEdit.getConfiguration(); @@ -323,7 +323,7 @@ public class SchematicCommands { public void save(Actor actor, LocalSession session, @Arg(desc = "File name.") String filename, - @Arg(desc = "Format name.", def = "sponge") + @Arg(desc = "Format name.", def = "fast") String formatName, @Switch(name = 'f', desc = "Overwrite an existing file.") boolean allowOverwrite, diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/BuiltInClipboardFormat.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/BuiltInClipboardFormat.java index d01db5dbb..cde698903 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/BuiltInClipboardFormat.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/BuiltInClipboardFormat.java @@ -24,8 +24,11 @@ import com.boydti.fawe.object.io.ResettableFileInputStream; import com.boydti.fawe.object.schematic.MinecraftStructure; import com.boydti.fawe.object.schematic.PNGWriter; import com.google.common.collect.ImmutableSet; +import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.NBTInputStream; import com.sk89q.jnbt.NBTOutputStream; +import com.sk89q.jnbt.NamedTag; +import com.sk89q.jnbt.Tag; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -35,6 +38,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -44,6 +48,44 @@ import java.util.zip.GZIPOutputStream; */ public enum BuiltInClipboardFormat implements ClipboardFormat { + FAST("fast", "fawe") { + + @Override + public String getPrimaryFileExtension() { + return "schem"; + } + + @Override + public ClipboardReader getReader(InputStream inputStream) throws IOException { + if (inputStream instanceof FileInputStream) { + inputStream = new ResettableFileInputStream((FileInputStream) inputStream); + } + BufferedInputStream buffered = new BufferedInputStream(inputStream); + NBTInputStream nbtStream = new NBTInputStream(new BufferedInputStream(new GZIPInputStream(buffered))); + return new FastSchematicReader(nbtStream); + } + + @Override + public ClipboardWriter getWriter(OutputStream outputStream) throws IOException { + OutputStream gzip; + if (outputStream instanceof PGZIPOutputStream || outputStream instanceof GZIPOutputStream) { + gzip = outputStream; + } else { + outputStream = new BufferedOutputStream(outputStream); + gzip = new PGZIPOutputStream(outputStream); + } + NBTOutputStream nbtStream = new NBTOutputStream(new BufferedOutputStream(gzip)); + return new FastSchematicWriter(nbtStream); + } + + @Override + public boolean isFormat(File file) { + String name = file.getName().toLowerCase(Locale.ROOT); + return name.endsWith(".schem") || name.endsWith(".sponge"); + } + + }, + /** * The Schematic format used by MCEdit. */ @@ -77,8 +119,49 @@ public enum BuiltInClipboardFormat implements ClipboardFormat { return name.endsWith(".schematic") || name.endsWith(".mcedit") || name.endsWith(".mce"); } }, + SPONGE_SCHEMATIC("sponge", "schem") { + @Override + public String getPrimaryFileExtension() { + return "schem"; + } + @Override + public ClipboardReader getReader(InputStream inputStream) throws IOException { + NBTInputStream nbtStream = new NBTInputStream(new GZIPInputStream(inputStream)); + return new SpongeSchematicReader(nbtStream); + } + + @Override + public ClipboardWriter getWriter(OutputStream outputStream) throws IOException { + NBTOutputStream nbtStream = new NBTOutputStream(new GZIPOutputStream(outputStream)); + return new SpongeSchematicWriter(nbtStream); + } + + @Override + public boolean isFormat(File file) { + try (NBTInputStream str = new NBTInputStream(new GZIPInputStream(new FileInputStream(file)))) { + NamedTag rootTag = str.readNamedTag(); + if (!rootTag.getName().equals("Schematic")) { + return false; + } + CompoundTag schematicTag = (CompoundTag) rootTag.getTag(); + + // Check + Map schematic = schematicTag.getValue(); + if (!schematic.containsKey("Version")) { + return false; + } + } catch (Exception e) { + return false; + } + + return true; + } + }, + + BROKENENTITY("brokenentity", "legacyentity", "le", "be", "brokenentities", "legacyentities") { + @Override public String getPrimaryFileExtension() { return "schem"; @@ -91,7 +174,9 @@ public enum BuiltInClipboardFormat implements ClipboardFormat { } BufferedInputStream buffered = new BufferedInputStream(inputStream); NBTInputStream nbtStream = new NBTInputStream(new BufferedInputStream(new GZIPInputStream(buffered))); - return new FastSchematicReader(nbtStream); + FastSchematicReader reader = new FastSchematicReader(nbtStream); + reader.setBrokenEntities(true); + return reader; } @Override @@ -104,13 +189,14 @@ public enum BuiltInClipboardFormat implements ClipboardFormat { gzip = new PGZIPOutputStream(outputStream); } NBTOutputStream nbtStream = new NBTOutputStream(new BufferedOutputStream(gzip)); - return new FastSchematicWriter(nbtStream); + FastSchematicWriter writer = new FastSchematicWriter(nbtStream); + writer.setBrokenEntities(true); + return writer; } @Override public boolean isFormat(File file) { - String name = file.getName().toLowerCase(Locale.ROOT); - return name.endsWith(".schem") || name.endsWith(".sponge"); + return false; } }, diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicReader.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicReader.java index c0c413689..e4674b85f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicReader.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicReader.java @@ -30,10 +30,12 @@ import com.boydti.fawe.object.io.FastByteArraysInputStream; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.IntTag; import com.sk89q.jnbt.NBTInputStream; +import com.sk89q.jnbt.NamedTag; import com.sk89q.jnbt.StringTag; import com.sk89q.jnbt.Tag; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.entity.BaseEntity; +import com.sk89q.worldedit.entity.Entity; import com.sk89q.worldedit.extension.input.InputParseException; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; @@ -45,6 +47,7 @@ import com.sk89q.worldedit.world.DataFixer; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.block.BlockTypesCache; import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.entity.EntityTypes; @@ -58,6 +61,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.OptionalInt; import java.util.UUID; import java.util.function.Function; @@ -70,9 +74,10 @@ public class FastSchematicReader extends NBTSchematicReader { private static final Logger log = LoggerFactory.getLogger(FastSchematicReader.class); private final NBTInputStream inputStream; - private DataFixer fixer = null; + private DataFixer fixer; private int dataVersion = -1; private int version = -1; + private int faweWritten = -1; private FastByteArrayOutputStream blocksOut; private FaweOutputStream blocks; @@ -92,6 +97,7 @@ public class FastSchematicReader extends NBTSchematicReader { private char[] palette; private char[] biomePalette; private BlockVector3 min = BlockVector3.ZERO; + private boolean brokenEntities = false; /** @@ -105,6 +111,10 @@ public class FastSchematicReader extends NBTSchematicReader { this.fixer = WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).getDataFixer(); } + public void setBrokenEntities(boolean brokenEntities) { + this.brokenEntities = brokenEntities; + } + private String fix(String palettePart) { if (fixer == null || dataVersion == -1) { return palettePart; @@ -133,7 +143,7 @@ public class FastSchematicReader extends NBTSchematicReader { return fixer.fixUp(DataFixer.FixTypes.BIOME, biomePalettePart, dataVersion); } - public StreamDelegate createDelegate() { + public StreamDelegate createVersionDelegate() { StreamDelegate root = new StreamDelegate(); StreamDelegate schematic = root.add("Schematic"); schematic.add("DataVersion").withInt((i, v) -> dataVersion = v); @@ -143,6 +153,12 @@ public class FastSchematicReader extends NBTSchematicReader { dataVersion = Constants.DATA_VERSION_MC_1_13_2; } }); + return root; + } + + public StreamDelegate createDelegate() { + StreamDelegate root = new StreamDelegate(); + StreamDelegate schematic = root.add("Schematic"); schematic.add("Width").withInt((i, v) -> width = v); schematic.add("Height").withInt((i, v) -> height = v); schematic.add("Length").withInt((i, v) -> length = v); @@ -152,17 +168,19 @@ public class FastSchematicReader extends NBTSchematicReader { metadata.add("WEOffsetX").withInt((i, v) -> offsetX = v); metadata.add("WEOffsetY").withInt((i, v) -> offsetY = v); metadata.add("WEOffsetZ").withInt((i, v) -> offsetZ = v); + metadata.add("FAWEVersion").withInt((i, v) -> faweWritten = v); StreamDelegate paletteDelegate = schematic.add("Palette"); paletteDelegate.withValue((ValueReader>) (ignore, v) -> { palette = new char[v.size()]; for (Entry entry : v.entrySet()) { - BlockState state = null; + BlockState state; + String palettePart = fix(entry.getKey()); try { - String palettePart = fix(entry.getKey()); state = BlockState.get(palettePart); - } catch (InputParseException e) { - e.printStackTrace(); + } catch (InputParseException ignored) { + log.warn("Invalid BlockState in palette: " + palettePart + ". Block will be replaced with air."); + state = BlockTypes.AIR.getDefaultState(); } int index = (int) entry.getValue(); palette[index] = (char) state.getOrdinal(); @@ -224,10 +242,14 @@ public class FastSchematicReader extends NBTSchematicReader { @Override public Clipboard read(UUID uuid, Function createOutput) throws IOException { StreamDelegate root = createDelegate(); + StreamDelegate versions = createVersionDelegate(); + inputStream.mark(Integer.MAX_VALUE); + inputStream.readNamedTagLazy(versions); + inputStream.reset(); inputStream.readNamedTagLazy(root); if (version != 1 && version != 2) { - throw new IOException("This schematic version is currently not supported"); + throw new IOException("This schematic version is currently not supported (" + version + ")"); } if (blocks != null) { @@ -240,9 +262,11 @@ public class FastSchematicReader extends NBTSchematicReader { biomes = null; BlockVector3 dimensions = BlockVector3.at(width, height, length); - BlockVector3 origin = BlockVector3.ZERO; + BlockVector3 origin; if (offsetX != Integer.MIN_VALUE && offsetY != Integer.MIN_VALUE && offsetZ != Integer.MIN_VALUE) { origin = BlockVector3.at(-offsetX, -offsetY, -offsetZ); + } else { + origin = BlockVector3.ZERO; } Clipboard clipboard = createOutput.apply(dimensions); @@ -352,7 +376,7 @@ public class FastSchematicReader extends NBTSchematicReader { if (id == null) { id = (StringTag) value.get("id"); if (id == null) { - return null; + continue; } } value.put("id", id); @@ -363,7 +387,26 @@ public class FastSchematicReader extends NBTSchematicReader { ent = fixEntity(ent); BaseEntity state = new BaseEntity(type, ent); Location loc = ent.getEntityLocation(clipboard); - clipboard.createEntity(loc, state); + if (brokenEntities) { + clipboard.createEntity(loc, state); + continue; + } + if (faweWritten == -1) { + int locX = loc.getBlockX(); + int locY = loc.getBlockY(); + int locZ = loc.getBlockZ(); + BlockVector3 max = min.add(dimensions).subtract(BlockVector3.ONE); + if (locX < min.getX() || locY < min.getY() || locZ < min.getZ() + || locX > max.getX() || locY > max.getY() || locZ > max.getZ()) { + for (Entity e : clipboard.getEntities()) { + clipboard.removeEntity(e); + } + log.error("Detected schematic entity outside clipboard region. FAWE will not load entities. " + + "Please try loading the schematic with the format \"legacyentity\""); + break; + } + } + clipboard.createEntity(loc.setPosition(loc.subtract(min.toVector3())), state); } else { log.debug("Invalid entity: " + id); } @@ -372,7 +415,7 @@ public class FastSchematicReader extends NBTSchematicReader { clipboard.setOrigin(origin); if (!min.equals(BlockVector3.ZERO)) { - new BlockArrayClipboard(clipboard, min); + clipboard = new BlockArrayClipboard(clipboard, min); } return clipboard; diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicWriter.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicWriter.java index 092bc94d0..6351c7f80 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicWriter.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/FastSchematicWriter.java @@ -19,10 +19,10 @@ package com.sk89q.worldedit.extent.clipboard.io; +import com.boydti.fawe.Fawe; import com.boydti.fawe.jnbt.streamer.IntValueReader; import com.boydti.fawe.object.FaweOutputStream; import com.boydti.fawe.util.IOUtil; -import com.google.common.collect.Maps; import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.IntArrayTag; import com.sk89q.jnbt.ListTag; @@ -39,6 +39,7 @@ import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.function.visitor.Order; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.biome.BiomeTypes; import com.sk89q.worldedit.world.block.BaseBlock; @@ -58,8 +59,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkNotNull; @@ -72,6 +71,7 @@ public class FastSchematicWriter implements ClipboardWriter { private static final int MAX_SIZE = Short.MAX_VALUE - Short.MIN_VALUE; private final NBTOutputStream outputStream; + private boolean brokenEntities = false; /** * Create a new schematic writer. @@ -83,6 +83,10 @@ public class FastSchematicWriter implements ClipboardWriter { this.outputStream = outputStream; } + public void setBrokenEntities(boolean brokenEntities) { + this.brokenEntities = brokenEntities; + } + @Override public void write(Clipboard clipboard) throws IOException { // For now always write the latest version. Maybe provide support for earlier if more appear. @@ -132,6 +136,7 @@ public class FastSchematicWriter implements ClipboardWriter { out1.writeNamedTag("WEOffsetX", offset.getBlockX()); out1.writeNamedTag("WEOffsetY", offset.getBlockY()); out1.writeNamedTag("WEOffsetZ", offset.getBlockZ()); + out1.writeNamedTag("FAWEVersion", Fawe.get().getVersion().build); }); ByteArrayOutputStream blocksCompressed = new ByteArrayOutputStream(); @@ -240,8 +245,12 @@ public class FastSchematicWriter implements ClipboardWriter { // Store our location data, overwriting any values.remove("id"); + Location loc = entity.getLocation(); + if (!brokenEntities) { + loc = loc.setPosition(loc.add(min.toVector3())); + } values.put("Id", new StringTag(state.getType().getId())); - values.put("Pos", writeVector(entity.getLocation())); + values.put("Pos", writeVector(loc)); values.put("Rotation", writeRotation(entity.getLocation())); CompoundTag entityTag = new CompoundTag(values); @@ -311,30 +320,6 @@ public class FastSchematicWriter implements ClipboardWriter { } } - private void writeEntities(Clipboard clipboard, NBTOutputStream schematic) throws IOException { - List entities = clipboard.getEntities().stream().map(e -> { - BaseEntity state = e.getState(); - if (state == null) { - return null; - } - Map values = Maps.newHashMap(); - CompoundTag rawData = state.getNbtData(); - if (rawData != null) { - values.putAll(rawData.getValue()); - } - values.remove("id"); - values.put("Id", new StringTag(state.getType().getId())); - values.put("Pos", writeVector(e.getLocation().toVector())); - values.put("Rotation", writeRotation(e.getLocation())); - - return new CompoundTag(values); - }).filter(Objects::nonNull).collect(Collectors.toList()); - if (entities.isEmpty()) { - return; - } - schematic.writeNamedTag("Entities", new ListTag(CompoundTag.class, entities)); - } - @Override public void close() throws IOException { outputStream.close(); diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java index 14f363009..e64bb3a97 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extent/clipboard/io/SchematicReader.java @@ -378,7 +378,7 @@ public class SchematicReader implements ClipboardReader { BlockVector3 min = BlockVector3.at(originX, originY, originZ); if (!min.equals(BlockVector3.ZERO)) { - new BlockArrayClipboard(clipboard, min); + clipboard = new BlockArrayClipboard(clipboard, min); } 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 c556febf9..00e7e8868 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,6 +19,7 @@ package com.sk89q.worldedit.extent.clipboard.io; +import com.boydti.fawe.Fawe; import com.google.common.collect.Maps; import com.sk89q.jnbt.ByteArrayTag; import com.sk89q.jnbt.CompoundTag; @@ -114,6 +115,7 @@ public class SpongeSchematicWriter implements ClipboardWriter { metadata.put("WEOffsetX", new IntTag(offset.getBlockX())); metadata.put("WEOffsetY", new IntTag(offset.getBlockY())); metadata.put("WEOffsetZ", new IntTag(offset.getBlockZ())); + metadata.put("FAWEVersion", new IntTag(Fawe.get().getVersion().build)); schematic.put("Metadata", new CompoundTag(metadata));