Add: LitematicConverter for converting .litematic to .schematic
This commit is contained in:
1 parent
aadec09c45
commit
b6b42f4ed0
1 file changed
+193
@@ -0,0 +1,193 @@
|
||||
package io.ib67.sfcraft.util;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.SneakyThrows;
|
||||
import net.minecraft.nbt.*;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A cleaned up version of https://github.com/GoldenDelicios/Lite2Edit
|
||||
* Uses {@link NbtCompound} from Minecraft.
|
||||
*/
|
||||
public class LitematicConverter implements AutoCloseable {
|
||||
public static final String NBT_LITEMATICA_ROOT = "";
|
||||
private final InputStream input;
|
||||
private final NbtSizeTracker sizeTracker;
|
||||
private NbtCompound root;
|
||||
|
||||
public LitematicConverter(InputStream input, NbtSizeTracker sizeTracker) {
|
||||
this.input = input;
|
||||
this.sizeTracker = sizeTracker;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public void read(
|
||||
BiConsumer<Integer, NbtCompound> schematicOutput
|
||||
) {
|
||||
if (root != null) throw new IllegalStateException("Litematica file is already read");
|
||||
root = NbtIo.readCompressed(input, sizeTracker).getCompound(NBT_LITEMATICA_ROOT);
|
||||
var dataVersion = root.getInt("MinecraftDataVersion");
|
||||
var regionsNbt = root.getList("Regions", NbtElement.COMPOUND_TYPE);
|
||||
var i = 0;
|
||||
for (NbtElement nbtElement : regionsNbt) {
|
||||
var compound = (NbtCompound) nbtElement;
|
||||
var j = i++;
|
||||
var schematic = convertRegionToSchematic(dataVersion, compound);
|
||||
schematicOutput.accept(j, schematic);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param region region
|
||||
* @return a path to temp file.
|
||||
*/
|
||||
private NbtCompound convertRegionToSchematic(int dataVersion, NbtCompound region) {
|
||||
var worldEditTag = new NbtCompound();
|
||||
|
||||
// prepare metadata
|
||||
var size = readSizeTuple(region);
|
||||
worldEditTag.put("Metadata", convertToWeMeta(size, region));
|
||||
worldEditTag.putInt("DataVersion", dataVersion);
|
||||
|
||||
// dimensional data.
|
||||
worldEditTag.putShort("Height", (short) Math.abs(size.y));
|
||||
worldEditTag.putShort("Length", (short) Math.abs(size.z));
|
||||
worldEditTag.putShort("Width", (short) Math.abs(size.x));
|
||||
|
||||
// block & tile entities
|
||||
var paletteNbt = region.getList("BlockStatePalette", NbtElement.COMPOUND_TYPE);
|
||||
var wePalette = convertToWEPalette(paletteNbt);
|
||||
worldEditTag.putInt("PaletteMax", wePalette.getKeys().size());
|
||||
worldEditTag.put("Palette", wePalette);
|
||||
var tileEntities = region.getList("TileEntities", NbtElement.COMPOUND_TYPE);
|
||||
worldEditTag.put("BlockEntities", convertToWETileEntities(tileEntities));
|
||||
worldEditTag.putInt("Version", 2);
|
||||
worldEditTag.putIntArray("Offset", new int[3]);
|
||||
worldEditTag.putByteArray("BlockData", convertToWEBlocks(size.x * size.y * size.z, region));
|
||||
var schematicsRoot = new NbtCompound();
|
||||
schematicsRoot.put("Schematic", worldEditTag);
|
||||
return schematicsRoot;
|
||||
}
|
||||
|
||||
private SizeTuple readSizeTuple(NbtCompound region) {
|
||||
var size = (NbtCompound) region.get("Size");
|
||||
var sizeX = size.getInt("x");
|
||||
var sizeY = size.getInt("y");
|
||||
var sizeZ = size.getInt("z");
|
||||
return new SizeTuple(sizeX, sizeY, sizeZ);
|
||||
}
|
||||
|
||||
private NbtList convertToWETileEntities(NbtList tileEntities) {
|
||||
var weTEs = new NbtList();
|
||||
for (NbtElement _tileEntity : tileEntities) {
|
||||
var tE = (NbtCompound) _tileEntity;
|
||||
tE.putIntArray("Pos", new int[]{
|
||||
tE.getInt("x"),
|
||||
tE.getInt("y"),
|
||||
tE.getInt("z")
|
||||
});
|
||||
tE.putString("Id", tE.getString("id"));
|
||||
// other properties
|
||||
tE.remove("x");
|
||||
tE.remove("y");
|
||||
tE.remove("z");
|
||||
tE.remove("id");
|
||||
weTEs.add(tE);
|
||||
}
|
||||
return weTEs;
|
||||
}
|
||||
|
||||
private NbtCompound convertToWEPalette(NbtList paletteNbt) {
|
||||
var wePalette = new NbtCompound();
|
||||
for (int i = 0; i < paletteNbt.size(); i++) {
|
||||
var entry = (NbtCompound) paletteNbt.get(i);
|
||||
var name = new StringBuilder(entry.getString("Name"));
|
||||
var properties = entry.getCompound("Properties");
|
||||
if (!properties.isEmpty()) {
|
||||
name.append("[");
|
||||
var props = new ArrayList<String>();
|
||||
for (String key : properties.getKeys()) {
|
||||
props.add(key + "=" + properties.getString(key));
|
||||
}
|
||||
name.append(props.stream().collect(Collectors.joining(",")));
|
||||
name.append("]");
|
||||
}
|
||||
wePalette.putInt(name.toString(), i);
|
||||
}
|
||||
return wePalette;
|
||||
}
|
||||
|
||||
private NbtElement convertToWeMeta(SizeTuple size, NbtCompound region) {
|
||||
var pos = (NbtCompound) region.get("Position");
|
||||
var nbt = new NbtCompound();
|
||||
nbt.putInt("WEOffsetX", pos.getInt("x") + size.x < 0 ? size.x + 1 : 0);
|
||||
nbt.putInt("WEOffsetY", pos.getInt("y") + size.y < 0 ? size.y + 1 : 0);
|
||||
nbt.putInt("WEOffsetZ", pos.getInt("z") + size.z < 0 ? size.z + 1 : 0);
|
||||
return nbt;
|
||||
}
|
||||
|
||||
private byte[] convertToWEBlocks(int totalBlocks, NbtCompound region) {
|
||||
var buf = Unpooled.buffer();
|
||||
var blockStates = region.getLongArray("BlockStates");
|
||||
|
||||
int bitsPerBlock = region.getList("BlockStatePalette", NbtElement.COMPOUND_TYPE).size();
|
||||
bitsPerBlock = Math.max(2, Integer.SIZE - Integer.numberOfTrailingZeros(bitsPerBlock - 1));
|
||||
|
||||
int bitCounts = 0, blockIterated = 0;
|
||||
long bitMask, bits = 0;
|
||||
for (long blockState : blockStates) {
|
||||
int remainingBits = bitCounts + 64;
|
||||
if (bitCounts != 0) {
|
||||
bitMask = (1L << (bitsPerBlock - bitCounts)) - 1;
|
||||
long newBits = (blockState & bitMask) << bitCounts;
|
||||
bits = bits | newBits;
|
||||
blockState = blockState >>> (bitsPerBlock - bitCounts);
|
||||
remainingBits -= bitsPerBlock;
|
||||
writeBlocks(buf, (short) bits);
|
||||
blockIterated++;
|
||||
}
|
||||
|
||||
bitMask = (1L << bitsPerBlock) - 1;
|
||||
while(remainingBits >= bitsPerBlock){
|
||||
bits = blockState & bitMask;
|
||||
blockState = blockState >>> bitsPerBlock;
|
||||
remainingBits -= bitsPerBlock;
|
||||
if(blockIterated >= totalBlocks) break;
|
||||
writeBlocks(buf, (short) bits);
|
||||
blockIterated++;
|
||||
}
|
||||
bits = blockState;
|
||||
bitCounts = remainingBits;
|
||||
}
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
private static int writeBlocks(ByteBuf buf, short block) {
|
||||
int b = block >>> 7;
|
||||
if (b == 0) {
|
||||
buf.writeByte(block);
|
||||
return 1;
|
||||
} else {
|
||||
buf.writeByte(block | 128);
|
||||
buf.writeByte(block);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
input.close();
|
||||
}
|
||||
|
||||
record SizeTuple(
|
||||
int x, int y, int z
|
||||
) {
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user