Mirror von
https://github.com/IntellectualSites/FastAsyncWorldEdit.git
synchronisiert 2024-11-19 09:20:08 +01:00
chore/feat: more work on the fast v3 reader
Dieser Commit ist enthalten in:
Ursprung
500812efe1
Commit
870a96e9c0
@ -1,45 +1,326 @@
|
||||
package com.fastasyncworldedit.core.extent.clipboard.io;
|
||||
|
||||
import com.fastasyncworldedit.core.extent.clipboard.LinearClipboard;
|
||||
import com.fastasyncworldedit.core.extent.clipboard.SimpleClipboard;
|
||||
import com.fastasyncworldedit.core.internal.io.VarIntStreamIterator;
|
||||
import com.fastasyncworldedit.core.math.MutableBlockVector3;
|
||||
import com.sk89q.jnbt.NBTConstants;
|
||||
import com.sk89q.jnbt.NBTInputStream;
|
||||
import com.sk89q.worldedit.WorldEdit;
|
||||
import com.sk89q.worldedit.extension.input.InputParseException;
|
||||
import com.sk89q.worldedit.extension.platform.Capability;
|
||||
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
|
||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||
import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader;
|
||||
import com.sk89q.worldedit.extent.clipboard.io.sponge.VersionedDataFixer;
|
||||
import com.sk89q.worldedit.internal.util.LogManagerCompat;
|
||||
import com.sk89q.worldedit.math.BlockVector3;
|
||||
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 org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* ClipboardReader for the Sponge Schematic Format v3.
|
||||
* Not necessarily much faster than {@link com.sk89q.worldedit.extent.clipboard.io.sponge.SpongeSchematicV3Reader}, but uses a
|
||||
* stream based approach to keep the memory overhead minimal (especially in larger schematics)
|
||||
*/
|
||||
@SuppressWarnings("removal") // JNBT
|
||||
public class FastSchematicReaderV3 implements ClipboardReader {
|
||||
|
||||
private final DataInputStream dataStream;
|
||||
private final NBTInputStream nbtStream;
|
||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||
|
||||
public FastSchematicReaderV3(DataInputStream dataInputStream, NBTInputStream inputStream) {
|
||||
this.dataStream = Objects.requireNonNull(dataInputStream, "dataInputStream");
|
||||
this.nbtStream = Objects.requireNonNull(inputStream, "inputStream");
|
||||
private final DataInputStream dataInputStream;
|
||||
private final NBTInputStream nbtInputStream;
|
||||
|
||||
private VersionedDataFixer dataFixer;
|
||||
private MutableBlockVector3 dimensions = MutableBlockVector3.at(0, 0, 0);
|
||||
private BlockVector3 offset;
|
||||
private BlockState[] blockPalette;
|
||||
private BiomeType[] biomePalette;
|
||||
private int dataVersion = -1;
|
||||
|
||||
private boolean blocksWritten = false;
|
||||
private boolean biomesWritten = false;
|
||||
private boolean entitiesWritten = false;
|
||||
|
||||
private boolean needAdditionalIterate = true;
|
||||
|
||||
public FastSchematicReaderV3(
|
||||
DataInputStream dataInputStream,
|
||||
NBTInputStream nbtInputStream
|
||||
) throws IOException {
|
||||
this.dataInputStream = Objects.requireNonNull(dataInputStream, "dataInputStream");
|
||||
this.nbtInputStream = Objects.requireNonNull(nbtInputStream, "nbtInputStream");
|
||||
if (!dataInputStream.markSupported()) {
|
||||
throw new IOException("InputStream does not support mark");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Clipboard read() throws IOException {
|
||||
final DataFixer dataFixer =
|
||||
WorldEdit.getInstance().getPlatformManager().queryCapability(Capability.WORLD_EDITING).getDataFixer();
|
||||
dataStream.skipNBytes(1 + 2); // 1 Byte = TAG_Compound, 2 Bytes = Short (Length of tag name = "")
|
||||
dataStream.skipNBytes(1 + 2 + 9); // as above + 9 bytes = "Schematic"
|
||||
public Clipboard read(final UUID uuid, final Function<BlockVector3, Clipboard> createOutput) throws IOException {
|
||||
dataInputStream.skipNBytes(1 + 2); // 1 Byte = TAG_Compound, 2 Bytes = Short (Length of tag name = "")
|
||||
dataInputStream.skipNBytes(1 + 2 + 9); // as above + 9 bytes = "Schematic"
|
||||
this.dataInputStream.mark(Integer.MAX_VALUE); // allow resets to basically the start of stream (file) - just skip header
|
||||
|
||||
byte type;
|
||||
while ((type = dataStream.readByte()) != NBTConstants.TYPE_END) {
|
||||
|
||||
Clipboard clipboard = null;
|
||||
|
||||
while (needAdditionalIterate) {
|
||||
this.needAdditionalIterate = false;
|
||||
this.dataInputStream.reset();
|
||||
this.dataInputStream.mark(Integer.MAX_VALUE);
|
||||
|
||||
while (dataInputStream.readByte() != NBTConstants.TYPE_END) {
|
||||
String tag = readTagName();
|
||||
switch (tag) {
|
||||
case "Version" -> this.dataInputStream.skipNBytes(4); // We know it's v3 (skip 4 byte version int)
|
||||
case "DataVersion" -> {
|
||||
this.dataVersion = this.dataInputStream.readInt();
|
||||
this.dataFixer = new VersionedDataFixer(
|
||||
this.dataVersion,
|
||||
WorldEdit
|
||||
.getInstance()
|
||||
.getPlatformManager()
|
||||
.queryCapability(Capability.WORLD_EDITING)
|
||||
.getDataFixer()
|
||||
);
|
||||
}
|
||||
case "Width", "Height", "Length" -> {
|
||||
if (clipboard != null) {
|
||||
continue;
|
||||
}
|
||||
if (tag.equals("Width")) {
|
||||
this.dimensions.mutX(this.dataInputStream.readShort() & 0xFFFF);
|
||||
} else if (tag.equals("Height")) {
|
||||
this.dimensions.mutY(this.dataInputStream.readShort() & 0xFFFF);
|
||||
} else {
|
||||
this.dimensions.mutZ(this.dataInputStream.readShort() & 0xFFFF);
|
||||
}
|
||||
if (areDimensionsAvailable()) {
|
||||
clipboard = createOutput.apply(this.dimensions);
|
||||
}
|
||||
}
|
||||
case "Offset" -> {
|
||||
this.dataInputStream.skipNBytes(4); // Array Length field (4 byte int)
|
||||
this.offset = BlockVector3.at(
|
||||
this.dataInputStream.readInt(),
|
||||
this.dataInputStream.readInt(),
|
||||
this.dataInputStream.readInt()
|
||||
);
|
||||
}
|
||||
case "Metadata" -> this.nbtInputStream.readTagPayloadLazy(NBTConstants.TYPE_COMPOUND, 0); // Skip metadata
|
||||
case "Blocks" -> {
|
||||
if (clipboard == null) {
|
||||
needAdditionalIterate = true;
|
||||
} else {
|
||||
this.readBlocks(clipboard);
|
||||
}
|
||||
}
|
||||
case "Biomes" -> {
|
||||
if (clipboard == null) {
|
||||
needAdditionalIterate = true;
|
||||
} else {
|
||||
this.readBiomes(clipboard);
|
||||
}
|
||||
}
|
||||
case "Entities" -> {
|
||||
if (clipboard == null) {
|
||||
needAdditionalIterate = true;
|
||||
} else {
|
||||
this.readEntities(clipboard);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
if (clipboard == null) {
|
||||
throw new NullPointerException("Failed to read schematic: Clipboard is null");
|
||||
}
|
||||
clipboard.setOrigin(this.offset);
|
||||
if (clipboard instanceof SimpleClipboard simpleClipboard && !this.offset.equals(BlockVector3.ZERO)) {
|
||||
clipboard = new BlockArrayClipboard(simpleClipboard, this.offset);
|
||||
}
|
||||
return clipboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt getDataVersion() {
|
||||
return this.dataVersion > -1 ? OptionalInt.of(this.dataVersion) : OptionalInt.empty();
|
||||
}
|
||||
|
||||
// TODO: BlockEntities
|
||||
private void readBlocks(Clipboard target) throws IOException {
|
||||
BiConsumer<Integer, Character> blockStateApplier;
|
||||
if (target instanceof LinearClipboard linearClipboard) {
|
||||
blockStateApplier = (dataIndex, paletteIndex) -> linearClipboard.setBlock(dataIndex, this.blockPalette[paletteIndex]);
|
||||
} else {
|
||||
blockStateApplier = (dataIndex, paletteIndex) -> {
|
||||
int y = dataIndex / (dimensions.x() * dimensions.z());
|
||||
int remainder = dataIndex - (y * dimensions.x() * dimensions.z());
|
||||
int z = remainder / dimensions.x();
|
||||
int x = remainder - z * dimensions.x();
|
||||
target.setBlock(x, y, z, this.blockPalette[paletteIndex]);
|
||||
};
|
||||
}
|
||||
this.blockPalette = new BlockState[BlockTypesCache.states.length];
|
||||
readPalette(
|
||||
() -> this.blockPalette.length == 0,
|
||||
() -> this.blocksWritten,
|
||||
() -> this.blocksWritten = true,
|
||||
(value, index) -> {
|
||||
value = dataFixer.fixUp(DataFixer.FixTypes.BLOCK_STATE, value);
|
||||
try {
|
||||
this.blockPalette[index] = BlockState.get(value);
|
||||
} catch (InputParseException e) {
|
||||
LOGGER.warn("Invalid BlockState in palette: {}. Block will be replaced with air.", value);
|
||||
this.blockPalette[index] = BlockTypes.AIR.getDefaultState();
|
||||
}
|
||||
},
|
||||
blockStateApplier,
|
||||
(type, tag) -> {
|
||||
if (!tag.equals("BlockEntities")) {
|
||||
LOGGER.warn("Found additional tag in block palette: {}. Will skip tag.", tag);
|
||||
try {
|
||||
this.nbtInputStream.readTagPayloadLazy(type, 0);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to skip additional tag", e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// TODO: Process block entities
|
||||
try {
|
||||
this.nbtInputStream.readTagPayloadLazy(type, 0);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void readBiomes(Clipboard target) throws IOException {
|
||||
BiConsumer<Integer, Character> biomeApplier;
|
||||
if (target instanceof LinearClipboard linearClipboard) {
|
||||
biomeApplier = (dataIndex, paletteIndex) -> linearClipboard.setBiome(dataIndex, this.biomePalette[paletteIndex]);
|
||||
} else {
|
||||
biomeApplier = (dataIndex, paletteIndex) -> {
|
||||
int y = dataIndex / (dimensions.x() * dimensions.z());
|
||||
int remainder = dataIndex - (y * dimensions.x() * dimensions.z());
|
||||
int z = remainder / dimensions.x();
|
||||
int x = remainder - z * dimensions.x();
|
||||
target.setBiome(x, y, z, this.biomePalette[paletteIndex]);
|
||||
};
|
||||
}
|
||||
this.biomePalette = new BiomeType[BiomeType.REGISTRY.size()];
|
||||
readPalette(
|
||||
() -> this.biomePalette.length == 0,
|
||||
() -> this.biomesWritten,
|
||||
() -> this.biomesWritten = true,
|
||||
(value, index) -> {
|
||||
value = dataFixer.fixUp(DataFixer.FixTypes.BIOME, value);
|
||||
BiomeType biomeType = BiomeTypes.get(value);
|
||||
if (biomeType == null) {
|
||||
biomeType = BiomeTypes.PLAINS;
|
||||
LOGGER.warn("Invalid biome type in palette: {}. Biome will be replaced with plains.", value);
|
||||
}
|
||||
this.biomePalette[index] = biomeType;
|
||||
},
|
||||
biomeApplier,
|
||||
(type, tag) -> {
|
||||
LOGGER.warn("Found additional tag in biome palette: {}. Will skip tag.", tag);
|
||||
try {
|
||||
this.nbtInputStream.readTagPayloadLazy(type, 0);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to skip additional tag", e);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void readEntities(Clipboard target) throws IOException {
|
||||
// TODO
|
||||
this.nbtInputStream.readTagPayloadLazy(NBTConstants.TYPE_LIST, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The `Palette` tag is required first, as that contains the information of the actual palette size.
|
||||
* Keeping the whole Data block in memory - which *could* be compressed - is just not it
|
||||
*
|
||||
* @param paletteInitializer Invoked for each 'Palette' entry using the actual palette value (e.g. block state) + index
|
||||
* @param paletteDataApplier Invoked for each 'Data' entry using the data index and the palette index at the data index
|
||||
*/
|
||||
private void readPalette(
|
||||
BooleanSupplier paletteAlreadyInitialized,
|
||||
BooleanSupplier dataAlreadyWritten,
|
||||
Runnable firstWrite,
|
||||
BiConsumer<String, Character> paletteInitializer,
|
||||
BiConsumer<Integer, Character> paletteDataApplier,
|
||||
BiConsumer<Byte, String> additionalTag
|
||||
) throws IOException {
|
||||
if (dataAlreadyWritten.getAsBoolean()) {
|
||||
return;
|
||||
}
|
||||
boolean hasPalette = paletteAlreadyInitialized.getAsBoolean();
|
||||
byte type;
|
||||
String tag;
|
||||
while ((type = this.dataInputStream.readByte()) != NBTConstants.TYPE_END) {
|
||||
tag = readTagName();
|
||||
if (tag.equals("Palette")) {
|
||||
if (hasPalette) {
|
||||
// Skip data, as palette already exists
|
||||
this.nbtInputStream.readTagPayloadLazy(NBTConstants.TYPE_COMPOUND, 0);
|
||||
} else {
|
||||
// Read all palette entries
|
||||
while (this.dataInputStream.readByte() != NBTConstants.TYPE_END) {
|
||||
String value = this.dataInputStream.readUTF();
|
||||
char index = (char) this.dataInputStream.readInt();
|
||||
paletteInitializer.accept(value, index);
|
||||
}
|
||||
hasPalette = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (tag.equals("Data")) {
|
||||
// No palette or dimensions are yet available - will need to read Data next round
|
||||
if (!hasPalette || !areDimensionsAvailable()) {
|
||||
this.needAdditionalIterate = true;
|
||||
return;
|
||||
}
|
||||
int length = this.dataInputStream.readInt();
|
||||
// Write data into clipboard
|
||||
firstWrite.run();
|
||||
int i = 0;
|
||||
for (var iter = new VarIntStreamIterator(this.dataInputStream, length); iter.hasNext(); i++) {
|
||||
paletteDataApplier.accept(i, (char) iter.nextInt());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
additionalTag.accept(type, tag);
|
||||
}
|
||||
}
|
||||
|
||||
private String readTagName() throws IOException {
|
||||
return dataInputStream.readUTF();
|
||||
}
|
||||
|
||||
private boolean areDimensionsAvailable() {
|
||||
return this.dimensions.x() != 0 && this.dimensions.y() != 0 && this.dimensions.z() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
nbtStream.close(); // closes the DataInputStream implicitly
|
||||
nbtInputStream.close(); // closes the DataInputStream implicitly
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
*/
|
||||
public class FastSchematicWriterV2 implements ClipboardWriter {
|
||||
|
||||
private static final int CURRENT_VERSION = 2;
|
||||
public static final int CURRENT_VERSION = 2;
|
||||
|
||||
private static final int MAX_SIZE = Short.MAX_VALUE - Short.MIN_VALUE;
|
||||
private final NBTOutputStream outputStream;
|
||||
|
@ -0,0 +1,70 @@
|
||||
package com.fastasyncworldedit.core.internal.io;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.PrimitiveIterator;
|
||||
|
||||
/**
|
||||
* Basically {@link com.sk89q.worldedit.internal.util.VarIntIterator} but backed by {@link java.io.InputStream}
|
||||
*/
|
||||
public class VarIntStreamIterator implements PrimitiveIterator.OfInt {
|
||||
|
||||
private final InputStream parent;
|
||||
private final int limit;
|
||||
private int index;
|
||||
private boolean hasNextInt;
|
||||
private int nextInt;
|
||||
|
||||
public VarIntStreamIterator(final InputStream parent, int limit) {
|
||||
this.parent = parent;
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (hasNextInt) {
|
||||
return true;
|
||||
}
|
||||
if (index >= limit) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
nextInt = readNextInt();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return hasNextInt = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int nextInt() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
hasNextInt = false;
|
||||
return nextInt;
|
||||
}
|
||||
|
||||
|
||||
private int readNextInt() throws IOException {
|
||||
int value = 0;
|
||||
for (int bitsRead = 0; ; bitsRead += 7) {
|
||||
if (index >= limit) {
|
||||
throw new IllegalStateException("Ran out of bytes while reading VarInt (probably corrupted data)");
|
||||
}
|
||||
byte next = (byte) this.parent.read();
|
||||
index++;
|
||||
value |= (next & 0x7F) << bitsRead;
|
||||
if (bitsRead > 7 * 5) {
|
||||
throw new IllegalStateException("VarInt too big (probably corrupted data)");
|
||||
}
|
||||
if ((next & 0x80) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -396,7 +396,7 @@ public class SchematicCommands {
|
||||
.isInSubDirectory(saveDir, file)) + ")"));
|
||||
return;
|
||||
}
|
||||
if (format == null) {
|
||||
if (format == null || !format.isFormat(file)) {
|
||||
format = ClipboardFormats.findByFile(file);
|
||||
if (format == null) {
|
||||
actor.print(Caption.of("worldedit.schematic.unknown-format", TextComponent.of(formatName)));
|
||||
|
@ -20,6 +20,7 @@
|
||||
package com.sk89q.worldedit.extent.clipboard.io;
|
||||
|
||||
import com.fastasyncworldedit.core.extent.clipboard.io.FastSchematicReaderV2;
|
||||
import com.fastasyncworldedit.core.extent.clipboard.io.FastSchematicReaderV3;
|
||||
import com.fastasyncworldedit.core.extent.clipboard.io.FastSchematicWriterV2;
|
||||
import com.fastasyncworldedit.core.extent.clipboard.io.FastSchematicWriterV3;
|
||||
import com.fastasyncworldedit.core.extent.clipboard.io.schematic.MinecraftStructure;
|
||||
@ -28,6 +29,7 @@ import com.fastasyncworldedit.core.internal.io.ResettableFileInputStream;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.sk89q.jnbt.CompoundTag;
|
||||
import com.sk89q.jnbt.IntTag;
|
||||
import com.sk89q.jnbt.NBTConstants;
|
||||
import com.sk89q.jnbt.NBTInputStream;
|
||||
import com.sk89q.jnbt.NBTOutputStream;
|
||||
import com.sk89q.jnbt.NamedTag;
|
||||
@ -41,11 +43,13 @@ import org.anarres.parallelgzip.ParallelGZIPOutputStream;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@ -55,13 +59,21 @@ import java.util.zip.GZIPOutputStream;
|
||||
/**
|
||||
* A collection of supported clipboard formats.
|
||||
*/
|
||||
@SuppressWarnings("removal") //FAWE: suppress JNBT deprecations
|
||||
public enum BuiltInClipboardFormat implements ClipboardFormat {
|
||||
|
||||
//FAWE start - register fast clipboard io
|
||||
FAST_NEW("new_fast") { // For testing purposes
|
||||
FAST_V3("fast", "fawe", "schem") { // For testing purposes
|
||||
|
||||
@Override
|
||||
public ClipboardReader getReader(final InputStream inputStream) throws IOException {
|
||||
return SPONGE_V3_SCHEMATIC.getReader(inputStream); // TODO: use FastSchematicReaderV3 when finished
|
||||
public ClipboardReader getReader(InputStream inputStream) throws IOException {
|
||||
if (inputStream instanceof FileInputStream fileInputStream) {
|
||||
inputStream = new ResettableFileInputStream(fileInputStream);
|
||||
}
|
||||
BufferedInputStream buffered = new BufferedInputStream(new GZIPInputStream(new BufferedInputStream(inputStream)));
|
||||
DataInputStream dataInputStream = new DataInputStream(buffered);
|
||||
NBTInputStream nbtStream = new NBTInputStream(buffered);
|
||||
return new FastSchematicReaderV3(dataInputStream, nbtStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -79,45 +91,41 @@ public enum BuiltInClipboardFormat implements ClipboardFormat {
|
||||
|
||||
@Override
|
||||
public boolean isFormat(final File file) {
|
||||
return FAST.isFormat(file);
|
||||
/**
|
||||
* TODO: test if this actually works
|
||||
* try (final DataInputStream stream = new DataInputStream(new GZIPInputStream(Files.newInputStream(file.toPath())));
|
||||
* final NBTInputStream nbt = new NBTInputStream(stream)) {
|
||||
* if (stream.readByte() != NBTConstants.TYPE_COMPOUND) {
|
||||
* return false;
|
||||
* }
|
||||
* stream.readShort(); // TAG name length ("" = 0), no need to read name as no bytes are written for root tag
|
||||
* if (stream.readByte() != NBTConstants.TYPE_COMPOUND) {
|
||||
* return false;
|
||||
* }
|
||||
* stream.readShort(); // TAG name length ("Schematic" = 9)
|
||||
* stream.skipNBytes(9); // "Schematic"
|
||||
*
|
||||
* // We can't guarantee the specific order of nbt data, so scan and skip, if required
|
||||
* do {
|
||||
* byte type = stream.readByte();
|
||||
* String name = stream.readUTF();
|
||||
* if (type == NBTConstants.TYPE_END) {
|
||||
* return false;
|
||||
* }
|
||||
* if (type == NBTConstants.TYPE_INT && name.equals("Version")) {
|
||||
* return stream.readInt() == FastSchematicWriterV3.CURRENT_VERSION;
|
||||
* }
|
||||
* nbt.readTagPayloadLazy(type, 1);
|
||||
* } while (true);
|
||||
* } catch (IOException ignored) {
|
||||
* }
|
||||
* return false;
|
||||
*/
|
||||
try (final DataInputStream stream = new DataInputStream(new GZIPInputStream(Files.newInputStream(file.toPath())));
|
||||
final NBTInputStream nbt = new NBTInputStream(stream)) {
|
||||
if (stream.readByte() != NBTConstants.TYPE_COMPOUND) {
|
||||
return false;
|
||||
}
|
||||
stream.skipNBytes(2); // TAG name length ("" = 0), no need to read name as no bytes are written for root tag
|
||||
if (stream.readByte() != NBTConstants.TYPE_COMPOUND) {
|
||||
return false;
|
||||
}
|
||||
stream.skipNBytes(2); // TAG name length ("Schematic" = 9)
|
||||
stream.skipNBytes(9); // "Schematic"
|
||||
|
||||
// We can't guarantee the specific order of nbt data, so scan and skip, if required
|
||||
do {
|
||||
byte type = stream.readByte();
|
||||
String name = stream.readUTF();
|
||||
if (type == NBTConstants.TYPE_END) {
|
||||
return false;
|
||||
}
|
||||
if (type == NBTConstants.TYPE_INT && name.equals("Version")) {
|
||||
return stream.readInt() == FastSchematicWriterV3.CURRENT_VERSION;
|
||||
}
|
||||
nbt.readTagPayloadLazy(type, 0);
|
||||
} while (true);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrimaryFileExtension() {
|
||||
return FAST.getPrimaryFileExtension();
|
||||
return "schem";
|
||||
}
|
||||
},
|
||||
FAST("fast", "fawe", "sponge", "schem") {
|
||||
FAST_V2("fast.2", "fawe.2", "schem.2") {
|
||||
@Override
|
||||
public String getPrimaryFileExtension() {
|
||||
return "schem";
|
||||
@ -148,8 +156,29 @@ public enum BuiltInClipboardFormat implements ClipboardFormat {
|
||||
|
||||
@Override
|
||||
public boolean isFormat(File file) {
|
||||
String name = file.getName().toLowerCase(Locale.ROOT);
|
||||
return name.endsWith(".schem") || name.endsWith(".sponge");
|
||||
try (final DataInputStream stream = new DataInputStream(new GZIPInputStream(Files.newInputStream(file.toPath())));
|
||||
final NBTInputStream nbt = new NBTInputStream(stream)) {
|
||||
if (stream.readByte() != NBTConstants.TYPE_COMPOUND) {
|
||||
return false;
|
||||
}
|
||||
stream.skipNBytes(2); // TAG name length ("Schematic" = 9)
|
||||
stream.skipNBytes(9); // "Schematic"
|
||||
|
||||
// We can't guarantee the specific order of nbt data, so scan and skip, if required
|
||||
do {
|
||||
byte type = stream.readByte();
|
||||
String name = stream.readUTF();
|
||||
if (type == NBTConstants.TYPE_END) {
|
||||
return false;
|
||||
}
|
||||
if (type == NBTConstants.TYPE_INT && name.equals("Version")) {
|
||||
return stream.readInt() == FastSchematicWriterV2.CURRENT_VERSION;
|
||||
}
|
||||
nbt.readTagPayloadLazy(type, 0);
|
||||
} while (true);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
},
|
||||
@ -231,7 +260,7 @@ public enum BuiltInClipboardFormat implements ClipboardFormat {
|
||||
* Avoid using with any large schematics/clipboards for reading/writing.
|
||||
*/
|
||||
@Deprecated
|
||||
SPONGE_V2_SCHEMATIC("slow", "safe", "sponge.2") {
|
||||
SPONGE_V2_SCHEMATIC("slow.2", "safe.2", "sponge.2") {
|
||||
@Override
|
||||
public String getPrimaryFileExtension() {
|
||||
return "schem";
|
||||
@ -271,8 +300,7 @@ public enum BuiltInClipboardFormat implements ClipboardFormat {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
SPONGE_V3_SCHEMATIC("sponge.3", "sponge", "schem") {
|
||||
|
||||
SPONGE_V3_SCHEMATIC("sponge.3", "slow", "safe") {
|
||||
@Override
|
||||
public String getPrimaryFileExtension() {
|
||||
return "schem";
|
||||
@ -299,7 +327,7 @@ public enum BuiltInClipboardFormat implements ClipboardFormat {
|
||||
return false;
|
||||
}
|
||||
Tag schematicTag = rootCompoundTag.getValue()
|
||||
.get("Schematic");
|
||||
.get("Schematic");
|
||||
if (!(schematicTag instanceof CompoundTag)) {
|
||||
return false;
|
||||
}
|
||||
@ -426,6 +454,17 @@ public enum BuiltInClipboardFormat implements ClipboardFormat {
|
||||
@Deprecated
|
||||
public static final BuiltInClipboardFormat SPONGE_SCHEMATIC = SPONGE_V2_SCHEMATIC;
|
||||
|
||||
//FAWE start
|
||||
/**
|
||||
* For backwards compatibility, this points to the fast implementation of the Sponge Schematic Specification (Version 2)
|
||||
* format. This should not be used going forwards.
|
||||
*
|
||||
* @deprecated Use {@link #FAST_V2} or {@link #FAST_V3}
|
||||
*/
|
||||
@Deprecated
|
||||
public static final BuiltInClipboardFormat FAST = FAST_V2;
|
||||
//FAWE end
|
||||
|
||||
private final ImmutableSet<String> aliases;
|
||||
|
||||
BuiltInClipboardFormat(String... aliases) {
|
||||
|
@ -62,56 +62,64 @@ import static com.sk89q.worldedit.extent.clipboard.io.SchematicNbtUtil.requireTa
|
||||
/**
|
||||
* Common code shared between schematic readers.
|
||||
*/
|
||||
class ReaderUtil {
|
||||
public class ReaderUtil { //FAWE - make public
|
||||
|
||||
private static final Logger LOGGER = LogManagerCompat.getLogger();
|
||||
|
||||
static void checkSchematicVersion(int version, CompoundTag schematicTag) throws IOException {
|
||||
int schematicVersion = requireTag(schematicTag.getValue(), "Version", IntTag.class)
|
||||
.getValue();
|
||||
.getValue();
|
||||
|
||||
checkState(
|
||||
version == schematicVersion,
|
||||
"Schematic is not version %s, but %s", version, schematicVersion
|
||||
version == schematicVersion,
|
||||
"Schematic is not version %s, but %s", version, schematicVersion
|
||||
);
|
||||
}
|
||||
|
||||
static VersionedDataFixer getVersionedDataFixer(Map<String, Tag> schematic, Platform platform,
|
||||
int liveDataVersion) throws IOException {
|
||||
static VersionedDataFixer getVersionedDataFixer(
|
||||
Map<String, Tag> schematic, Platform platform,
|
||||
int liveDataVersion
|
||||
) throws IOException {
|
||||
//FAWE - delegate to new method
|
||||
return getVersionedDataFixer(requireTag(schematic, "DataVersion", IntTag.class).getValue(), platform, liveDataVersion);
|
||||
}
|
||||
|
||||
//FAWE start - make getVersionedDataFixer without schematic compound + public
|
||||
public static VersionedDataFixer getVersionedDataFixer(int schematicDataVersion, Platform platform, int liveDataVersion) {
|
||||
DataFixer fixer = null;
|
||||
int dataVersion = requireTag(schematic, "DataVersion", IntTag.class).getValue();
|
||||
if (dataVersion < 0) {
|
||||
if (schematicDataVersion < 0) {
|
||||
LOGGER.warn(
|
||||
"Schematic has an unknown data version ({}). Data may be incompatible.",
|
||||
dataVersion
|
||||
"Schematic has an unknown data version ({}). Data may be incompatible.",
|
||||
schematicDataVersion
|
||||
);
|
||||
} else if (dataVersion > liveDataVersion) {
|
||||
} else if (schematicDataVersion > liveDataVersion) {
|
||||
LOGGER.warn(
|
||||
"Schematic was made in a newer Minecraft version ({} > {})."
|
||||
+ " Data may be incompatible.",
|
||||
dataVersion, liveDataVersion
|
||||
"Schematic was made in a newer Minecraft version ({} > {})."
|
||||
+ " Data may be incompatible.",
|
||||
schematicDataVersion, liveDataVersion
|
||||
);
|
||||
} else if (dataVersion < liveDataVersion) {
|
||||
} else if (schematicDataVersion < liveDataVersion) {
|
||||
fixer = platform.getDataFixer();
|
||||
if (fixer != null) {
|
||||
LOGGER.debug(
|
||||
"Schematic was made in an older Minecraft version ({} < {}),"
|
||||
+ " will attempt DFU.",
|
||||
dataVersion, liveDataVersion
|
||||
"Schematic was made in an older Minecraft version ({} < {}),"
|
||||
+ " will attempt DFU.",
|
||||
schematicDataVersion, liveDataVersion
|
||||
);
|
||||
} else {
|
||||
LOGGER.info(
|
||||
"Schematic was made in an older Minecraft version ({} < {}),"
|
||||
+ " but DFU is not available. Data may be incompatible.",
|
||||
dataVersion, liveDataVersion
|
||||
"Schematic was made in an older Minecraft version ({} < {}),"
|
||||
+ " but DFU is not available. Data may be incompatible.",
|
||||
schematicDataVersion, liveDataVersion
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new VersionedDataFixer(dataVersion, fixer);
|
||||
return new VersionedDataFixer(schematicDataVersion, fixer);
|
||||
}
|
||||
//FAWE end
|
||||
|
||||
static Map<Integer, BlockState> decodePalette(
|
||||
Map<String, Tag> paletteObject, VersionedDataFixer fixer
|
||||
Map<String, Tag> paletteObject, VersionedDataFixer fixer
|
||||
) throws IOException {
|
||||
Map<Integer, BlockState> palette = new HashMap<>();
|
||||
|
||||
@ -136,15 +144,15 @@ class ReaderUtil {
|
||||
}
|
||||
|
||||
static void initializeClipboardFromBlocks(
|
||||
Clipboard clipboard, Map<Integer, BlockState> palette, byte[] blocks, ListTag tileEntities,
|
||||
VersionedDataFixer fixer, boolean dataIsNested
|
||||
Clipboard clipboard, Map<Integer, BlockState> palette, byte[] blocks, ListTag tileEntities,
|
||||
VersionedDataFixer fixer, boolean dataIsNested
|
||||
) throws IOException {
|
||||
Map<BlockVector3, Map<String, Tag>> tileEntitiesMap = new HashMap<>();
|
||||
if (tileEntities != null) {
|
||||
List<Map<String, Tag>> tileEntityTags = tileEntities.getValue().stream()
|
||||
.map(tag -> (CompoundTag) tag)
|
||||
.map(CompoundTag::getValue)
|
||||
.collect(Collectors.toList());
|
||||
.map(tag -> (CompoundTag) tag)
|
||||
.map(CompoundTag::getValue)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Map<String, Tag> tileEntity : tileEntityTags) {
|
||||
int[] pos = requireTag(tileEntity, "Pos", IntArrayTag.class).getValue();
|
||||
@ -168,8 +176,8 @@ class ReaderUtil {
|
||||
values.put("id", tileEntity.get("Id"));
|
||||
if (fixer.isActive()) {
|
||||
tileEntity = ((CompoundTag) AdventureNBTConverter.fromAdventure(fixer.fixUp(
|
||||
DataFixer.FixTypes.BLOCK_ENTITY,
|
||||
new CompoundTag(values).asBinaryTag()
|
||||
DataFixer.FixTypes.BLOCK_ENTITY,
|
||||
new CompoundTag(values).asBinaryTag()
|
||||
))).getValue();
|
||||
} else {
|
||||
tileEntity = values;
|
||||
@ -191,7 +199,7 @@ class ReaderUtil {
|
||||
Map<String, Tag> tileEntity = tileEntitiesMap.get(offsetPos);
|
||||
if (tileEntity != null) {
|
||||
clipboard.setBlock(
|
||||
offsetPos, state.toBaseBlock(new CompoundTag(tileEntity))
|
||||
offsetPos, state.toBaseBlock(new CompoundTag(tileEntity))
|
||||
);
|
||||
} else {
|
||||
clipboard.setBlock(offsetPos, state);
|
||||
@ -222,8 +230,10 @@ class ReaderUtil {
|
||||
return BlockVector3.at(parts[0], parts[1], parts[2]);
|
||||
}
|
||||
|
||||
static void readEntities(BlockArrayClipboard clipboard, List<Tag> entList,
|
||||
VersionedDataFixer fixer, boolean positionIsRelative) throws IOException {
|
||||
static void readEntities(
|
||||
BlockArrayClipboard clipboard, List<Tag> entList,
|
||||
VersionedDataFixer fixer, boolean positionIsRelative
|
||||
) throws IOException {
|
||||
if (entList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@ -249,20 +259,21 @@ class ReaderUtil {
|
||||
}
|
||||
CompoundTag dataTag = dataTagBuilder.putString("id", id).build();
|
||||
dataTag = ((CompoundTag) AdventureNBTConverter.fromAdventure(fixer.fixUp(
|
||||
DataFixer.FixTypes.ENTITY,
|
||||
dataTag.asBinaryTag()
|
||||
DataFixer.FixTypes.ENTITY,
|
||||
dataTag.asBinaryTag()
|
||||
)));
|
||||
|
||||
EntityType entityType = EntityTypes.get(id);
|
||||
if (entityType != null) {
|
||||
Location location = NBTConversions.toLocation(clipboard,
|
||||
requireTag(tags, "Pos", ListTag.class),
|
||||
requireTag(dataTag.getValue(), "Rotation", ListTag.class)
|
||||
Location location = NBTConversions.toLocation(
|
||||
clipboard,
|
||||
requireTag(tags, "Pos", ListTag.class),
|
||||
requireTag(dataTag.getValue(), "Rotation", ListTag.class)
|
||||
);
|
||||
BaseEntity state = new BaseEntity(entityType, dataTag);
|
||||
if (positionIsRelative) {
|
||||
location = location.setPosition(
|
||||
location.toVector().add(clipboard.getMinimumPoint().toVector3())
|
||||
location.toVector().add(clipboard.getMinimumPoint().toVector3())
|
||||
);
|
||||
}
|
||||
clipboard.createEntity(location, state);
|
||||
@ -274,4 +285,5 @@ class ReaderUtil {
|
||||
|
||||
private ReaderUtil() {
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,12 +24,12 @@ import com.sk89q.worldedit.world.DataFixer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
final class VersionedDataFixer {
|
||||
public final class VersionedDataFixer { //FAWE - public
|
||||
private final int dataVersion;
|
||||
@Nullable
|
||||
private final DataFixer fixer;
|
||||
|
||||
VersionedDataFixer(int dataVersion, @Nullable DataFixer fixer) {
|
||||
public VersionedDataFixer(int dataVersion, @Nullable DataFixer fixer) { //FAWE - public
|
||||
this.dataVersion = dataVersion;
|
||||
this.fixer = fixer;
|
||||
}
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren