diff --git a/src/main/java/us/myles/ViaVersion/api/type/Type.java b/src/main/java/us/myles/ViaVersion/api/type/Type.java index 81dc97ed3..784806f9a 100644 --- a/src/main/java/us/myles/ViaVersion/api/type/Type.java +++ b/src/main/java/us/myles/ViaVersion/api/type/Type.java @@ -60,7 +60,7 @@ public abstract class Type implements ByteBufReader, ByteBufWriter { public static final Type ROTATION = new EulerAngleType(); public static final Type VECTOR = new VectorType(); public static final Type NBT = new NBTType(); - public static final Type> NBT_ARRAY = new NBTArrayType(); + public static final Type NBT_ARRAY = new ArrayType<>(Type.NBT); public static final Type OPTIONAL_UUID = new OptUUIDType(); public static final Type OPTIONAL_POSITION = new OptPositionType(); diff --git a/src/main/java/us/myles/ViaVersion/api/type/types/minecraft/NBTArrayType.java b/src/main/java/us/myles/ViaVersion/api/type/types/minecraft/NBTArrayType.java deleted file mode 100644 index a39794169..000000000 --- a/src/main/java/us/myles/ViaVersion/api/type/types/minecraft/NBTArrayType.java +++ /dev/null @@ -1,33 +0,0 @@ -package us.myles.ViaVersion.api.type.types.minecraft; - -import io.netty.buffer.ByteBuf; -import org.spacehq.opennbt.tag.builtin.CompoundTag; -import us.myles.ViaVersion.api.type.Type; - -import java.util.ArrayList; -import java.util.List; - -public class NBTArrayType extends Type> { - public NBTArrayType() { - super("NBT Array", List.class); - } - - @Override - public List read(ByteBuf buffer) throws Exception { - int amount = Type.VAR_INT.read(buffer); - - List nbtData = new ArrayList<>(); - for (int i = 0; i < amount; i++) - nbtData.add(Type.NBT.read(buffer)); - - return nbtData; - } - - @Override - public void write(ByteBuf buffer, List list) throws Exception { - Type.VAR_INT.write(buffer, list.size()); - - for (CompoundTag tag : list) - Type.NBT.write(buffer, tag); - } -} diff --git a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_3to1_9_1_2/ChunkSection1_9_1_2.java b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_3to1_9_1_2/ChunkSection1_9_1_2.java new file mode 100644 index 000000000..4cff93fd6 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_3to1_9_1_2/ChunkSection1_9_1_2.java @@ -0,0 +1,197 @@ +package us.myles.ViaVersion.protocols.protocol1_9_3to1_9_1_2; + +import com.google.common.collect.Lists; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import us.myles.ViaVersion.api.minecraft.chunks.NibbleArray; +import us.myles.ViaVersion.api.type.Type; + +import java.util.List; + +public class ChunkSection1_9_1_2 { + /** + * Size (dimensions) of blocks in a chunks section. + */ + public static final int SIZE = 16 * 16 * 16; // width * depth * height + /** + * Length of the sky and block light nibble arrays. + */ + public static final int LIGHT_LENGTH = 16 * 16 * 16 / 2; // size * size * size / 2 (nibble bit count) + /** + * Length of the block data array. + */ + + private final List palette = Lists.newArrayList(); + private final int[] blocks; + private final NibbleArray blockLight; + private NibbleArray skyLight; + + public ChunkSection1_9_1_2() { + this.blocks = new int[SIZE]; + this.blockLight = new NibbleArray(SIZE); + palette.add(0); // AIR + } + + /** + * Set a block in the chunks + * + * @param x Block X + * @param y Block Y + * @param z Block Z + * @param type The type of the block + * @param data The data value of the block + */ + public void setBlock(int x, int y, int z, int type, int data) { + setBlock(index(x, y, z), type, data); + } + + public int getBlockId(int x, int y, int z){ + int index = blocks[index(x, y, z)]; + return palette.indexOf(index) >> 4; + } + + /** + * Set a block in the chunks based on the index + * + * @param idx Index + * @param type The type of the block + * @param data The data value of the block + */ + public void setBlock(int idx, int type, int data) { + int hash = type << 4 | (data & 0xF); + int index = palette.indexOf(hash); + if (index == -1) { + index = palette.size(); + palette.add(hash); + } + + blocks[idx] = index; + } + + /** + * Set the block light array + * + * @param data The value to set the block light to + */ + public void setBlockLight(byte[] data) { + blockLight.setHandle(data); + } + + /** + * Set the sky light array + * + * @param data The value to set the sky light to + */ + public void setSkyLight(byte[] data) { + if (data.length != LIGHT_LENGTH) throw new IllegalArgumentException("Data length != " + LIGHT_LENGTH); + this.skyLight = new NibbleArray(data); + } + + private int index(int x, int y, int z) { + return z << 8 | y << 4 | x; + } + + /** + * Write the blocks to a buffer. + * + * @param output The buffer to write to. + * @throws Exception Throws if it failed to write. + */ + public void writeBlocks(ByteBuf output) throws Exception { + // Write bits per block + int bitsPerBlock = 4; + while (palette.size() > 1 << bitsPerBlock) { + bitsPerBlock += 1; + } + long maxEntryValue = (1L << bitsPerBlock) - 1; + output.writeByte(bitsPerBlock); + + // Write pallet (or not) + Type.VAR_INT.write(output, palette.size()); + for (int mappedId : palette) { + Type.VAR_INT.write(output, mappedId); + } + + int length = (int) Math.ceil(SIZE * bitsPerBlock / 64.0); + Type.VAR_INT.write(output, length); + long[] data = new long[length]; + for (int index = 0; index < blocks.length; index++) { + int value = blocks[index]; + int bitIndex = index * bitsPerBlock; + int startIndex = bitIndex / 64; + int endIndex = ((index + 1) * bitsPerBlock - 1) / 64; + int startBitSubIndex = bitIndex % 64; + data[startIndex] = data[startIndex] & ~(maxEntryValue << startBitSubIndex) | ((long) value & maxEntryValue) << startBitSubIndex; + if (startIndex != endIndex) { + int endBitSubIndex = 64 - startBitSubIndex; + data[endIndex] = data[endIndex] >>> endBitSubIndex << endBitSubIndex | ((long) value & maxEntryValue) >> endBitSubIndex; + } + } + for (long l : data) { + Type.LONG.write(output, l); + } + } + + /** + * Write the block light to a buffer + * + * @param output The buffer to write to + */ + public void writeBlockLight(ByteBuf output) { + output.writeBytes(blockLight.getHandle()); + } + + /** + * Write the sky light to a buffer + * + * @param output The buffer to write to + */ + public void writeSkyLight(ByteBuf output) { + output.writeBytes(skyLight.getHandle()); + } + + /** + * Check if sky light is present + * + * @return True if skylight is present + */ + public boolean hasSkyLight() { + return skyLight != null; + } + + /** + * Get expected size of this chunks section. + * + * @return Amount of bytes sent by this section + * @throws Exception If it failed to calculate bits properly + */ + public int getExpectedSize() throws Exception { + int bitsPerBlock = palette.size() > 255 ? 16 : 8; + int bytes = 1; // bits per block + bytes += paletteBytes(); // palette + bytes += countBytes(bitsPerBlock == 16 ? SIZE * 2 : SIZE); // block data length + bytes += (palette.size() > 255 ? 2 : 1) * SIZE; // block data + bytes += LIGHT_LENGTH; // block light + bytes += hasSkyLight() ? LIGHT_LENGTH : 0; // sky light + return bytes; + } + + private int paletteBytes() throws Exception { + // Count bytes used by pallet + int bytes = countBytes(palette.size()); + for (int mappedId : palette) { + bytes += countBytes(mappedId); + } + return bytes; + } + + private int countBytes(int value) throws Exception { + // Count amount of bytes that would be sent if the value were sent as a VarInt + ByteBuf buf = Unpooled.buffer(); + Type.VAR_INT.write(buf, value); + buf.readerIndex(0); + int bitCount = buf.readableBytes(); + buf.release(); + return bitCount; + } +} diff --git a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9to1_8/types/ChunkType.java b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9to1_8/types/ChunkType.java index 2fb99634c..fc08862be 100644 --- a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9to1_8/types/ChunkType.java +++ b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9to1_8/types/ChunkType.java @@ -179,7 +179,7 @@ public class ChunkType extends PartialType { output.writeBytes(chunk.getBiomeData()); } - Type.NBT_ARRAY.write(buf, tags); + Type.NBT_ARRAY.write(output, tags.toArray(new CompoundTag[0])); }