Skip to content

Commit

Permalink
Fix: schematic encoder with help from chatgpt
Browse files Browse the repository at this point in the history
  • Loading branch information
iceBear67 committed Jan 7, 2025
1 parent 92fe6f9 commit f76c975
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.netty.buffer.Unpooled;
import lombok.SneakyThrows;
import net.minecraft.nbt.*;
import net.minecraft.network.PacketByteBuf;
import org.apache.commons.compress.utils.Lists;

import java.io.InputStream;
Expand Down Expand Up @@ -63,7 +64,7 @@ protected NbtCompound convertRegionToSchematic(int dataVersion, NbtCompound regi
worldEditTag.put("BlockEntities", convertToWETileEntities(tileEntities));
worldEditTag.putInt("Version", 2);
worldEditTag.putIntArray("Offset", new int[3]);
worldEditTag.putByteArray("BlockData", convertToWEBlocks(Math.abs(size.x * size.y * size.z), region));
worldEditTag.putByteArray("BlockData", convertToWEBlocks(size, region));
var schematicsRoot = new NbtCompound();
schematicsRoot.put("Schematic", worldEditTag);
return schematicsRoot;
Expand Down Expand Up @@ -94,7 +95,7 @@ protected NbtList convertToWETileEntities(NbtList tileEntities) {
tE.remove("y");
tE.remove("z");
tE.remove("id");
weTE.put("Data",tE);
weTE.put("Data", tE);
weTEs.add(weTE);
}
return weTEs;
Expand Down Expand Up @@ -129,60 +130,40 @@ protected NbtElement convertToWeMeta(SizeTuple size, NbtCompound region) {
return nbt;
}

protected byte[] convertToWEBlocks(int totalBlocks, NbtCompound region) {
var buf = Unpooled.buffer();
protected byte[] convertToWEBlocks(SizeTuple size, NbtCompound region) {
var blockCount = Math.abs(size.x * size.y * size.z);
var blockStates = region.getLongArray("BlockStates");

int bitsPerBlock = region.getList("BlockStatePalette", NbtElement.COMPOUND_TYPE).size();
bitsPerBlock = Math.max(2, Integer.SIZE - Integer.numberOfLeadingZeros(bitsPerBlock - 1));

int bitCounts = 0, blockIterated = 0;
long bitMask, bits = 0;
for (long blockState : blockStates) {
int remainingBits = bitCounts + 64;
if (bitCounts != 0) {
bitMask = (1 << (bitsPerBlock - bitCounts)) - 1;
long newBits = (blockState & bitMask) << bitCounts;
bits = bits | newBits;
blockState = blockState >>> (bitsPerBlock - bitCounts);
remainingBits -= bitsPerBlock;
writeBlocks(buf, (short) bits);
blockIterated++;
}

bitMask = (1 << bitsPerBlock) - 1;
while (remainingBits >= bitsPerBlock) {
bits = blockState & bitMask;
blockState = blockState >>> bitsPerBlock;
remainingBits -= bitsPerBlock;
if (blockIterated >= totalBlocks) break;
writeBlocks(buf, (short) bits);
blockIterated++;
int maxEntryValue = (1 << bitsPerBlock) - 1;
var buffer = new PacketByteBuf(Unpooled.buffer());
for (int index = 0; index < blockCount; index++) {
int startBit = index * bitsPerBlock;
int startLongIndex = startBit / 64;
int startBitOffset = startBit % 64;
int endBit = startBit + bitsPerBlock - 1;
int endLongIndex = endBit / 64;

int value;
if (startLongIndex == endLongIndex) {
value = (int) ((blockStates[startLongIndex] >>> startBitOffset) & maxEntryValue);
} else {
int bitsInFirstPart = 64 - startBitOffset; // 第一个 long 提取的位数
long firstPart = blockStates[startLongIndex] >>> startBitOffset;
long secondPart = blockStates[endLongIndex] & ((1L << (bitsPerBlock - bitsInFirstPart)) - 1);
value = (int) ((firstPart | (secondPart << bitsInFirstPart)) & maxEntryValue);
}
bits = blockState;
bitCounts = remainingBits;
}
return buf.array();
}

protected 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;
buffer.writeVarInt(value);
}
return buffer.array();
}

@Override
public void close() throws Exception {
input.close();
}

record SizeTuple(
protected record SizeTuple(
int x, int y, int z
) {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
package io.ib67.sfcraft.util.litematic;

import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import lombok.extern.log4j.Log4j2;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.nbt.NbtElement;
import net.minecraft.nbt.NbtSizeTracker;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.math.Vec3i;

import java.io.InputStream;

@Log4j2
public class LitematicConverterV3 extends LitematicConverter {
public LitematicConverterV3(InputStream input, NbtSizeTracker sizeTracker) {
super(input, sizeTracker);
}

@Override
protected NbtCompound convertRegionToSchematic(int dataVersion, NbtCompound region) {
var schematicsTag = new NbtCompound();
Expand All @@ -34,15 +41,55 @@ protected NbtCompound convertRegionToSchematic(int dataVersion, NbtCompound regi

var blocksNbt = new NbtCompound();
blocksNbt.put("Palette", wePalette);
blocksNbt.putByteArray("Data", convertToWEBlocks(Math.abs(size.x() * size.y() * size.z()), region));
var weBlockData = convertToWEBlocks(size, region);
blocksNbt.putByteArray("Data", weBlockData);
blocksNbt.put("BlockEntities", convertToWETileEntities(tileEntities));

validateData(wePalette, weBlockData, Math.abs(size.x()), Math.abs(size.z()));
schematicsTag.put("Blocks", blocksNbt);
var schematicsRoot = new NbtCompound();
schematicsRoot.put("Schematic", schematicsTag);
return schematicsRoot;
}

private void validateData(
NbtCompound wePalette,
byte[] weBlockData,
int width,
int length
) {
// find invalid palette ids
var buf = new PacketByteBuf(Unpooled.wrappedBuffer(weBlockData));
var paletteIds = new Int2ObjectOpenHashMap<>();
var touchedIds = new IntOpenHashSet();
for (String key : wePalette.getKeys()) {
paletteIds.put(wePalette.getInt(key), key);
}
int counter = 0;
try {
while (buf.isReadable()) {
counter++;
var id = buf.readVarInt();
if (!paletteIds.containsKey(id)) {
log.error("Cannot find id {} at {}th block in palette, pos: {}", id, counter, decodePositionFromDataIndex(width, length, counter));
} else {
touchedIds.add(id);
}
}
} catch (Exception e) {
log.error("Error validing schematic", e);
}
paletteIds.keySet().intStream().filter(it->!touchedIds.contains(it))
.forEach(it-> log.error("Unused palette id: {}, blockState: {}", it, paletteIds.get(it)));
}
private static Vec3i decodePositionFromDataIndex(int width, int length, int index) {
// index = (y * width * length) + (z * width) + x
int y = index / (width * length);
int remainder = index - (y * width * length);
int z = remainder / width;
int x = remainder - z * width;
return new Vec3i(x, y, z);
}

@Override
protected NbtElement convertToWeMeta(SizeTuple size, NbtCompound region) {
var pos = (NbtCompound) region.get("Position");
Expand Down

0 comments on commit f76c975

Please sign in to comment.