From f258cf8c896072a5aa84dad189e82cc46372b45a Mon Sep 17 00:00:00 2001 From: Matsv Date: Mon, 8 Aug 2016 17:38:11 +0200 Subject: [PATCH] Stop depending on versions for the ChunkTypes. fix cast exceptions by using a ChunkType in front of it. --- .../api/minecraft/chunks/Chunk.java | 6 + .../Protocol1_9_1_2TO1_9_3_4.java | 52 +++- .../chunks/Chunk1_9_3_4.java | 10 +- .../chunks/ChunkSection1_9_3_4.java | 286 ++++++++++++++++-- .../types/Chunk1_9_3_4Type.java | 96 ++++-- .../Protocol1_9_3TO1_9_1_2.java | 3 +- .../chunks/Chunk1_9to1_8.java | 9 + 7 files changed, 407 insertions(+), 55 deletions(-) diff --git a/src/main/java/us/myles/ViaVersion/api/minecraft/chunks/Chunk.java b/src/main/java/us/myles/ViaVersion/api/minecraft/chunks/Chunk.java index 634b9c343..ef9fe2654 100644 --- a/src/main/java/us/myles/ViaVersion/api/minecraft/chunks/Chunk.java +++ b/src/main/java/us/myles/ViaVersion/api/minecraft/chunks/Chunk.java @@ -1,5 +1,9 @@ package us.myles.ViaVersion.api.minecraft.chunks; +import org.spacehq.opennbt.tag.builtin.CompoundTag; + +import java.util.List; + public interface Chunk { int getX(); @@ -14,4 +18,6 @@ public interface Chunk { byte[] getBiomeData(); int getBitmask(); + + List getBlockEntities(); } diff --git a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/Protocol1_9_1_2TO1_9_3_4.java b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/Protocol1_9_1_2TO1_9_3_4.java index 87617f53a..97e95a36f 100644 --- a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/Protocol1_9_1_2TO1_9_3_4.java +++ b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/Protocol1_9_1_2TO1_9_3_4.java @@ -11,11 +11,10 @@ import us.myles.ViaVersion.api.remapper.PacketRemapper; import us.myles.ViaVersion.api.type.Type; import us.myles.ViaVersion.packets.State; import us.myles.ViaVersion.protocols.protocol1_9_1_2to1_9_3_4.chunks.BlockEntity; -import us.myles.ViaVersion.protocols.protocol1_9_1_2to1_9_3_4.chunks.Chunk1_9_3_4; import us.myles.ViaVersion.protocols.protocol1_9_1_2to1_9_3_4.types.Chunk1_9_3_4Type; +import us.myles.ViaVersion.protocols.protocol1_9_3to1_9_1_2.storage.ClientWorld; public class Protocol1_9_1_2TO1_9_3_4 extends Protocol { - public static Type CHUNK = new Chunk1_9_3_4Type(); @Override protected void registerPackets() { @@ -60,8 +59,50 @@ public class Protocol1_9_1_2TO1_9_3_4 extends Protocol { handler(new PacketHandler() { @Override public void handle(PacketWrapper wrapper) throws Exception { - Chunk chunk = wrapper.passthrough(CHUNK); - BlockEntity.handle(((Chunk1_9_3_4) chunk).getBlockEntities(), wrapper.user()); + ClientWorld clientWorld = wrapper.user().get(ClientWorld.class); + + Chunk1_9_3_4Type type = new Chunk1_9_3_4Type(clientWorld); + Chunk chunk = wrapper.passthrough(type); + + BlockEntity.handle(chunk.getBlockEntities(), wrapper.user()); + } + }); + } + }); + + // Join (save dimension id) + registerOutgoing(State.PLAY, 0x23, 0x23, new PacketRemapper() { + @Override + public void registerMap() { + map(Type.INT); // 0 - Entity ID + map(Type.UNSIGNED_BYTE); // 1 - Gamemode + map(Type.INT); // 2 - Dimension + + handler(new PacketHandler() { + @Override + public void handle(PacketWrapper wrapper) throws Exception { + ClientWorld clientChunks = wrapper.user().get(ClientWorld.class); + + int dimensionId = wrapper.get(Type.INT, 1); + clientChunks.setEnvironment(dimensionId); + } + }); + } + }); + + // Respawn (save dimension id) + registerOutgoing(State.PLAY, 0x33, 0x33, new PacketRemapper() { + @Override + public void registerMap() { + map(Type.INT); // 0 - Dimension ID + + handler(new PacketHandler() { + @Override + public void handle(PacketWrapper wrapper) throws Exception { + ClientWorld clientWorld = wrapper.user().get(ClientWorld.class); + + int dimensionId = wrapper.get(Type.INT, 0); + clientWorld.setEnvironment(dimensionId); } }); } @@ -70,6 +111,7 @@ public class Protocol1_9_1_2TO1_9_3_4 extends Protocol { @Override public void init(UserConnection userConnection) { - + if (!userConnection.has(ClientWorld.class)) + userConnection.put(new ClientWorld(userConnection)); } } diff --git a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/chunks/Chunk1_9_3_4.java b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/chunks/Chunk1_9_3_4.java index 1abfcb43a..49b003b32 100644 --- a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/chunks/Chunk1_9_3_4.java +++ b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/chunks/Chunk1_9_3_4.java @@ -15,15 +15,11 @@ public class Chunk1_9_3_4 implements Chunk { private boolean groundUp; private int bitmask; private ChunkSection1_9_3_4[] sections; - List blockEntities; + private byte[] biomeData; + private List blockEntities; @Override public boolean isBiomeData() { - return false; - } - - @Override - public byte[] getBiomeData() { - return new byte[0]; + return biomeData != null; } } diff --git a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/chunks/ChunkSection1_9_3_4.java b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/chunks/ChunkSection1_9_3_4.java index 4f90852c7..4ea0b0afb 100644 --- a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/chunks/ChunkSection1_9_3_4.java +++ b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/chunks/ChunkSection1_9_3_4.java @@ -1,41 +1,293 @@ package us.myles.ViaVersion.protocols.protocol1_9_1_2to1_9_3_4.chunks; +import com.google.common.collect.Lists; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Getter; import us.myles.ViaVersion.api.minecraft.chunks.ChunkSection; +import us.myles.ViaVersion.api.minecraft.chunks.NibbleArray; +import us.myles.ViaVersion.api.type.Type; + +import java.util.List; public class ChunkSection1_9_3_4 implements ChunkSection { - private final byte[] data; + /** + * 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. + */ + @Getter + private final List palette = Lists.newArrayList(); + private final int[] blocks; + private final NibbleArray blockLight; + private NibbleArray skyLight; - public ChunkSection1_9_3_4(byte[] data) { - this.data = data; + public ChunkSection1_9_3_4() { + this.blocks = new int[SIZE]; + this.blockLight = new NibbleArray(SIZE); + palette.add(0); // AIR } - public byte[] getData() { - return data; + /** + * 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); } - @Override public int getBlockId(int x, int y, int z) { - throw new UnsupportedOperationException("Invalid chunk type!"); + int index = blocks[index(x, y, z)]; + return palette.get(index) >> 4; } - @Override + /** + * 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 y << 8 | z << 4 | x; + } + + /** + * Read blocks from input stream. + * This reads all the block related data: + *
    + *
  • Block length/palette type
  • + *
  • Palette
  • + *
  • Block hashes/palette reference
  • + *
+ * + * @param input The buffer to read from. + * @throws Exception + */ + public void readBlocks(ByteBuf input) throws Exception { + palette.clear(); + + // Reaad bits per block + int bitsPerBlock = input.readUnsignedByte(); + long maxEntryValue = (1L << bitsPerBlock) - 1; + + if (bitsPerBlock == 0) { + bitsPerBlock = 13; + } + if (bitsPerBlock < 4) { + bitsPerBlock = 4; + } + if (bitsPerBlock > 8) { + bitsPerBlock = 13; + } + int paletteLength = Type.VAR_INT.read(input); + // Read palette + for (int i = 0; i < paletteLength; i++) { + if (bitsPerBlock != 13) { + palette.add(Type.VAR_INT.read(input)); + } else { + Type.VAR_INT.read(input); + } + } + + // Read blocks + Long[] blockData = Type.LONG_ARRAY.read(input); + if (blockData.length > 0) { + for (int i = 0; i < blocks.length; i++) { + int bitIndex = i * bitsPerBlock; + int startIndex = bitIndex / 64; + int endIndex = ((i + 1) * bitsPerBlock - 1) / 64; + int startBitSubIndex = bitIndex % 64; + int val; + if (startIndex == endIndex) { + val = (int) (blockData[startIndex] >>> startBitSubIndex & maxEntryValue); + } else { + int endBitSubIndex = 64 - startBitSubIndex; + val = (int) ((blockData[startIndex] >>> startBitSubIndex | blockData[endIndex] << endBitSubIndex) & maxEntryValue); + } + + if (bitsPerBlock == 13) { + int type = val >> 4; + int data = val & 0xF; + + setBlock(i, type, data); + } else { + blocks[i] = val; + } + } + } + } + + /** + * Read block light from buffer. + * + * @param input The buffer to read from + */ + public void readBlockLight(ByteBuf input) { + byte[] handle = new byte[LIGHT_LENGTH]; + input.readBytes(handle); + blockLight.setHandle(handle); + } + + /** + * Read sky light from buffer. + * Note: Only sent in overworld! + * + * @param input The buffer to read from + */ + public void readSkyLight(ByteBuf input) { + byte[] handle = new byte[LIGHT_LENGTH]; + input.readBytes(handle); + if (skyLight != null) { + skyLight.setHandle(handle); + return; + } + + this.skyLight = new NibbleArray(handle); + } + + /** + * 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 { - throw new UnsupportedOperationException("Invalid chunk type!"); + // 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); + } } - @Override - public void writeBlockLight(ByteBuf output) throws Exception { - throw new UnsupportedOperationException("Invalid chunk type!"); + /** + * Write the block light to a buffer + * + * @param output The buffer to write to + */ + public void writeBlockLight(ByteBuf output) { + output.writeBytes(blockLight.getHandle()); } - @Override + /** + * 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() { - throw new UnsupportedOperationException("Invalid chunk type!"); + return skyLight != null; } - @Override - public void writeSkyLight(ByteBuf output) throws Exception { - throw new UnsupportedOperationException("Invalid chunk type!"); + /** + * 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_9_1_2to1_9_3_4/types/Chunk1_9_3_4Type.java b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/types/Chunk1_9_3_4Type.java index 947d737cf..684f1e4a3 100644 --- a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/types/Chunk1_9_3_4Type.java +++ b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_1_2to1_9_3_4/types/Chunk1_9_3_4Type.java @@ -1,56 +1,102 @@ package us.myles.ViaVersion.protocols.protocol1_9_1_2to1_9_3_4.types; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.bukkit.World; import org.spacehq.opennbt.tag.builtin.CompoundTag; import us.myles.ViaVersion.api.minecraft.chunks.Chunk; +import us.myles.ViaVersion.api.minecraft.chunks.ChunkSection; +import us.myles.ViaVersion.api.type.PartialType; import us.myles.ViaVersion.api.type.Type; import us.myles.ViaVersion.api.type.types.minecraft.BaseChunkType; import us.myles.ViaVersion.protocols.protocol1_9_1_2to1_9_3_4.chunks.Chunk1_9_3_4; import us.myles.ViaVersion.protocols.protocol1_9_1_2to1_9_3_4.chunks.ChunkSection1_9_3_4; +import us.myles.ViaVersion.protocols.protocol1_9_3to1_9_1_2.storage.ClientWorld; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; import java.util.List; -public class Chunk1_9_3_4Type extends BaseChunkType { - public Chunk1_9_3_4Type() { - super("1.9.3 Chunk"); +public class Chunk1_9_3_4Type extends PartialType { + + public Chunk1_9_3_4Type(ClientWorld param) { + super(param, Chunk.class); } @Override - public Chunk read(ByteBuf input) throws Exception { + public Chunk read(ByteBuf input, ClientWorld world) throws Exception { int chunkX = input.readInt(); int chunkZ = input.readInt(); boolean groundUp = input.readBoolean(); int primaryBitmask = Type.VAR_INT.read(input); - int size = Type.VAR_INT.read(input); + Type.VAR_INT.read(input); - byte[] sections = new byte[size]; - input.readBytes(sections); - - int blockEntities = Type.VAR_INT.read(input); - List nbtData = new ArrayList<>(); - for (int i = 0; i < blockEntities; i++) { - nbtData.add(Type.NBT.read(input)); + BitSet usedSections = new BitSet(16); + ChunkSection1_9_3_4[] sections = new ChunkSection1_9_3_4[16]; + // Calculate section count from bitmask + for (int i = 0; i < 16; i++) { + if ((primaryBitmask & (1 << i)) != 0) { + usedSections.set(i); + } } - return new Chunk1_9_3_4(chunkX, chunkZ, groundUp, primaryBitmask, new ChunkSection1_9_3_4[] {new ChunkSection1_9_3_4(sections)}, nbtData); + + // Read sections + for (int i = 0; i < 16; i++) { + if (!usedSections.get(i)) continue; // Section not set + ChunkSection1_9_3_4 section = new ChunkSection1_9_3_4(); + sections[i] = section; + section.readBlocks(input); + section.readBlockLight(input); + if (world.getEnvironment() == World.Environment.NORMAL) { + section.readSkyLight(input); + } + } + + byte[] biomeData = groundUp ? new byte[256] : null; + if (groundUp) { + input.readBytes(biomeData); + } + + List nbtData = Arrays.asList(Type.NBT_ARRAY.read(input)); + + return new Chunk1_9_3_4(chunkX, chunkZ, groundUp, primaryBitmask, sections, biomeData, nbtData); } @Override - public void write(ByteBuf buffer, Chunk input) throws Exception { - if (!(input instanceof Chunk1_9_3_4)) - throw new Exception("Tried to send the wrong chunk type from 1.9.3-4 chunk: " + input.getClass()); - Chunk1_9_3_4 chunk = (Chunk1_9_3_4) input; + public void write(ByteBuf output, ClientWorld world, Chunk chunk) throws Exception { + output.writeInt(chunk.getX()); + output.writeInt(chunk.getZ()); - buffer.writeInt(chunk.getX()); - buffer.writeInt(chunk.getZ()); + output.writeBoolean(chunk.isGroundUp()); + Type.VAR_INT.write(output, chunk.getBitmask()); - buffer.writeBoolean(chunk.isGroundUp()); - Type.VAR_INT.write(buffer, chunk.getBitmask()); + ByteBuf buf = Unpooled.buffer(); + for (int i = 0; i < 16; i++) { + ChunkSection section = chunk.getSections()[i]; + if (section == null) continue; // Section not set + section.writeBlocks(buf); + section.writeBlockLight(buf); - Type.VAR_INT.write(buffer, chunk.getSections()[0].getData().length); - buffer.writeBytes(chunk.getSections()[0].getData()); + if (!section.hasSkyLight()) continue; // No sky light, we're done here. + section.writeSkyLight(buf); - // no block entities as it's earlier + } + buf.readerIndex(0); + Type.VAR_INT.write(output, buf.readableBytes() + (chunk.isBiomeData() ? 256 : 0)); + output.writeBytes(buf); + buf.release(); // release buffer + + // Write biome data + if (chunk.isBiomeData()) { + output.writeBytes(chunk.getBiomeData()); + } + + // Don't write block entities + } + + @Override + public Class getBaseClass() { + return BaseChunkType.class; } } diff --git a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_3to1_9_1_2/Protocol1_9_3TO1_9_1_2.java b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_3to1_9_1_2/Protocol1_9_3TO1_9_1_2.java index b00d9ab48..564c2ff09 100644 --- a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_3to1_9_1_2/Protocol1_9_3TO1_9_1_2.java +++ b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9_3to1_9_1_2/Protocol1_9_3TO1_9_1_2.java @@ -142,6 +142,7 @@ public class Protocol1_9_3TO1_9_1_2 extends Protocol { @Override public void init(UserConnection user) { - user.put(new ClientWorld(user)); + if (!user.has(ClientWorld.class)) + user.put(new ClientWorld(user)); } } diff --git a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9to1_8/chunks/Chunk1_9to1_8.java b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9to1_8/chunks/Chunk1_9to1_8.java index 5a254119c..ea6d5815c 100644 --- a/src/main/java/us/myles/ViaVersion/protocols/protocol1_9to1_8/chunks/Chunk1_9to1_8.java +++ b/src/main/java/us/myles/ViaVersion/protocols/protocol1_9to1_8/chunks/Chunk1_9to1_8.java @@ -3,8 +3,12 @@ package us.myles.ViaVersion.protocols.protocol1_9to1_8.chunks; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; +import org.spacehq.opennbt.tag.builtin.CompoundTag; import us.myles.ViaVersion.api.minecraft.chunks.Chunk; +import java.util.Collections; +import java.util.List; + @RequiredArgsConstructor @Getter @ToString @@ -46,4 +50,9 @@ public class Chunk1_9to1_8 implements Chunk { public int getBitmask() { return primaryBitmask; } + + @Override + public List getBlockEntities() { + return Collections.emptyList(); + } }