Archived
0

Merge branch 'world-loader-anvil' into proto_1.12.2

This commit is contained in:
2019-01-29 11:43:51 +03:00
51 changed files with 2811 additions and 287 deletions

View File

@@ -6,4 +6,5 @@ dependencies {
/* Components */
compile (group: 'com.google.code.gson', name: 'gson', version: '2.8.5')
compile (group: 'net.sf.trove4j', name: 'trove4j', version: '3.0.3')
}

View File

@@ -1,18 +1,19 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-07-25
*/
package mc.core.network.proto_1_12_2;
import com.flowpowered.nbt.Tag;
import com.flowpowered.nbt.stream.NBTInputStream;
import lombok.extern.slf4j.Slf4j;
import mc.core.network.NetInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public abstract class NetInputStream_p340 extends NetInputStream {
private NBTInputStream nbtInputStream;
@Override
public int readVarInt(AtomicInteger countReadBytes) {
int numRead = 0;
@@ -59,4 +60,23 @@ public abstract class NetInputStream_p340 extends NetInputStream {
public UUID readUUID() {
return new UUID(readLong(), readLong());
}
@Override
public Tag<?> readNBT() {
if (nbtInputStream == null) {
try {
nbtInputStream = new NBTInputStream(this, false);
} catch (IOException e) {
log.error("Create NBT stream", e);
return null;
}
}
try {
return nbtInputStream.readTag();
} catch (IOException e) {
log.error("Read NBT", e);
return null;
}
}
}

View File

@@ -1,13 +1,18 @@
package mc.core.network.proto_1_12_2;
import com.flowpowered.nbt.Tag;
import com.flowpowered.nbt.stream.NBTOutputStream;
import lombok.extern.slf4j.Slf4j;
import mc.core.network.NetOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
@Slf4j
public abstract class NetOutputStream_p340 extends NetOutputStream {
private NBTOutputStream nbtOutputStream;
@Override
public void writeVarInt(int value) {
while ((value & -128) != 0) {
@@ -37,4 +42,22 @@ public abstract class NetOutputStream_p340 extends NetOutputStream {
writeLong(uuid.getMostSignificantBits());
writeLong(uuid.getLeastSignificantBits());
}
@Override
public void writeNBT(Tag<?> tag) {
if (nbtOutputStream == null) {
try {
nbtOutputStream = new NBTOutputStream(this, false);
} catch (IOException e) {
log.error("Create NBT stream", e);
return;
}
}
try {
nbtOutputStream.writeTag(tag);
} catch (IOException e) {
log.error("Write NBT", e);
}
}
}

View File

@@ -1,16 +1,17 @@
/*
* DmitriyMX <dimon550@gmail.com>
* 2018-07-21
*/
package mc.core.network.proto_1_12_2.packets;
import com.flowpowered.nbt.CompoundTag;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TIntArrayList;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import mc.core.network.NetOutputStream;
import mc.core.network.SCPacket;
import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream;
import mc.core.utils.NibbleArray;
import mc.core.world.block.Block;
import mc.core.world.block.BlockLocation;
import mc.core.world.block.BlockType;
import mc.core.world.chunk.Chunk;
import mc.core.world.chunk.ChunkSection;
@@ -80,10 +81,6 @@ public class ChunkDataPacket implements SCPacket {
private Chunk chunk;
private List<ChunkSection> sectionList;
private int serializeBlockState(BlockType blockType) {
return (blockType.getId() << 4) | blockType.getMeta();
}
public void setChunk(Chunk chunk) {
this.sectionList = null;
this.chunk = chunk;
@@ -96,13 +93,18 @@ public class ChunkDataPacket implements SCPacket {
@Override
public void writeSelf(NetOutputStream netStream) {
if (sectionList == null && chunk == null) {
log.warn("Empty chunk data!"); //TODO для такого нужна заглушка
return;
}
netStream.writeInt(x); // Chunk X
netStream.writeInt(z); // Chunk Y
netStream.writeBoolean(initChunk); // Init Chunk
int maxH = 0;
int bitMask = 0;
if (sectionList == null && chunk != null) {
int bitMask = 0;
for (int h = 15; h >= 0; h--) {
bitMask = bitMask << 1;
ChunkSection chunkSection = chunk.getChunkSection(h);
@@ -113,11 +115,8 @@ public class ChunkDataPacket implements SCPacket {
bitMask |= 0x00;
}
}
netStream.writeVarInt(bitMask); // Primary Bit Mask
} else if (sectionList != null && chunk == null) {
sectionList.sort(Comparator.comparingInt(ChunkSection::getY));
int bitMask = 0;
for (int h = 15, i = 0; h >= 0; h--) {
bitMask = bitMask << 1;
ChunkSection chunkSection = sectionList.get(i);
@@ -128,16 +127,15 @@ public class ChunkDataPacket implements SCPacket {
bitMask |= 0x00;
}
}
netStream.writeVarInt(bitMask); // Primary Bit Mask
} else {
log.warn("Empty chunk data");
return;
}
netStream.writeVarInt(bitMask); // Primary Bit Mask
final ByteArrayOutputNetStream data = new ByteArrayOutputNetStream();
int dataItems = 0;
final int airBlockPalette = serializeBlockState(BlockType.AIR);
final ByteArrayOutputNetStream biomes = new ByteArrayOutputNetStream();
boolean biomeWrite = true;
List<CompoundTag> nbtList = new ArrayList<>();
for (int h = 0; h < maxH; h++) {
ChunkSection chunkSection = null;
@@ -152,64 +150,30 @@ public class ChunkDataPacket implements SCPacket {
continue;
}
final List<Integer> palette = new ArrayList<>();
palette.add(airBlockPalette);
final ByteArrayOutputNetStream dataArray = new ByteArrayOutputNetStream();
final ByteArrayOutputNetStream blockLight = new ByteArrayOutputNetStream();
final ByteArrayOutputNetStream skyLight = new ByteArrayOutputNetStream();
final ByteArrayOutputNetStream biomes = new ByteArrayOutputNetStream();
long dataValueCompacted = 0;
int blockLightCompacted = 0;
int skyLightCompacted = 0;
int idxHalfLong = 0;
int idxHalfByte = 0;
boolean biomeFinally = false;
final PalettedChunkSection palettedChunkSection = new PalettedChunkSection();
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
Block block = chunkSection.getBlock(x, y, z);
int blockState = serializeBlockState(block.getType());
int currentIndexPaletteBlock;
if (!palette.contains(blockState)) {
palette.add(blockState);
currentIndexPaletteBlock = palette.size()-1;
} else {
currentIndexPaletteBlock = palette.indexOf(blockState);
palettedChunkSection.addBlock(
block,
chunkSection.getSkyLight(x, y, z)
);
CompoundTag nbt = block.getNBTData();
if (nbt != null) {
nbtList.add(nbt);
}
if (idxHalfLong == 0) {
dataValueCompacted = currentIndexPaletteBlock;
idxHalfLong++;
} else if (idxHalfLong > 0 && idxHalfLong < 15) {
dataValueCompacted = (dataValueCompacted << 4) | currentIndexPaletteBlock;
idxHalfLong++;
} else {
dataValueCompacted = (dataValueCompacted << 4) | currentIndexPaletteBlock;
dataArray.writeLong(dataValueCompacted);
idxHalfLong = 0;
dataItems++;
}
if (idxHalfByte == 0) {
blockLightCompacted = block.getLight();
skyLightCompacted = chunkSection.getSkyLight(x, y, z);
idxHalfByte++;
} else {
blockLightCompacted = (blockLightCompacted << 4) | block.getLight();
blockLight.writeByte(blockLightCompacted);
skyLightCompacted = (skyLightCompacted << 4) | chunkSection.getSkyLight(x, y, z);
skyLight.writeByte(skyLightCompacted);
idxHalfByte = 0;
}
if (!biomeFinally) {
biomes.writeByte(chunk.getBiome(x, z).getId());
if (biomeWrite) {
biomes.writeByte(chunk.getBiome(
(chunk.getX() << 4) + x,
(chunk.getZ() << 4) + z
).getId());
if (x == 15 && z == 15) {
biomeFinally = true;
biomeWrite = false;
}
}
}
@@ -217,30 +181,137 @@ public class ChunkDataPacket implements SCPacket {
}
// <Chunk Section>
// <Palette>
data.writeUnsignedByte(4); // Bits Per Block
data.writeVarInt(palette.size()); // Size of palette
palette.forEach(data::writeVarInt); // Palette
// </Palette>
// <Data Array>
data.writeVarInt(dataItems); // Size of Data Array
data.writeBytes(dataArray.toByteArray()); // Data Array
// </Data Array>
// <Block Light>
data.writeBytes(blockLight.toByteArray());
// </Block Light>
// <Sky Light>
data.writeBytes(skyLight.toByteArray());
// </Sky Light>
palettedChunkSection.writeToNetStream(data);
// </Chunk Section>
// <Biomes>
data.writeBytes(biomes.toByteArray());
// </Biomes>
}
// <Biomes>
data.writeBytes(biomes.toByteArray());
// </Biomes>
netStream.writeVarInt(data.size()); // Size of Data
netStream.writeBytes(data.toByteArray()); // Data
netStream.writeVarInt(0); // Number of block entities
/* writeNBT */
netStream.writeVarInt(nbtList.size()); // Number of block entities
// <NBT>
for (CompoundTag compoundTag : nbtList) {
netStream.writeNBT(compoundTag);
}
// </NBT>
}
@Override
public String toString() {
return "ChunkDataPacket{" +
"x=" + x +
", z=" + z +
", chunk=" + chunk +
'}';
}
private class PalettedChunkSection {
private TIntList palette = new TIntArrayList();
private byte[] blocks = new byte[4096];
private NibbleArray blockLight = new NibbleArray();
private NibbleArray skyLight = new NibbleArray();
private int coordsToIndex(BlockLocation location) {
return coordsToIndex(location.getX(), location.getY(), location.getZ());
}
private int coordsToIndex(int x, int y, int z) {
return y << 8 | z << 4 | x;
}
private int serializeBlockState(BlockType blockType) {
return (blockType.getId() << 4) | blockType.getMeta();
}
byte addBlockType(BlockType blockType) {
int blockState = serializeBlockState(blockType);
int idx = palette.indexOf(blockState);
if (idx == -1) {
palette.add(blockState);
idx = palette.size()-1;
}
return (byte) idx;
}
void addBlock(Block block, int skyLight) {
BlockLocation location = new BlockLocation(
block.getLocation().getX() - ((block.getLocation().getX() >> 4) << 4),
block.getLocation().getY() - ((block.getLocation().getY() >> 4) << 4),
block.getLocation().getZ() - ((block.getLocation().getZ() >> 4) << 4)
);
blocks[coordsToIndex(location)] = addBlockType(block.getType());
blockLight.set(location, block.getLight());
this.skyLight.set(location, skyLight);
}
void writeToNetStream(final NetOutputStream netOutputStream) {
int bitsPerBlock = 4;
if (palette.size() > 15) {
if (palette.size() <= 31)
bitsPerBlock = 5;
else if (palette.size() <= 63)
bitsPerBlock = 6;
else if (palette.size() <= 127)
bitsPerBlock = 7;
else if (palette.size() <= 255)
bitsPerBlock = 8;
}
// <Palette>
netOutputStream.writeUnsignedByte(bitsPerBlock); // Bits Per Block
netOutputStream.writeVarInt(palette.size()); // Size of palette
palette.forEach(value -> { netOutputStream.writeVarInt(value); return true; }); // Palette
// </Palette>
// <Data Array>
final int dataLength = (4096/*16*16*16*/ * bitsPerBlock) / 64/*size of long in bits*/;
netOutputStream.writeVarInt(dataLength); // Size of Data Array
// <Array>
long value = 0;
int lastPos = 0;
boolean fairy = false;
long fairyValue = 0;
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
final int blockNumber = (((y << 4) + z) << 4) + x;
final int startLong = ( blockNumber * bitsPerBlock ) / 64;
final int startOffset = ( blockNumber * bitsPerBlock ) % 64;
final int endLong = ((blockNumber + 1) * bitsPerBlock - 1) / 64;
final long idxBlockInPalette = blocks[coordsToIndex(x, y, z)];
if (startLong != lastPos) {
netOutputStream.writeLong(value);
lastPos = startLong;
if (fairy) {
value = fairyValue;
fairy = false;
} else {
value = 0;
}
}
value |= (idxBlockInPalette << startOffset);
if (startLong != endLong) {
fairyValue = idxBlockInPalette >> (64 - startOffset);
fairy = true;
}
}
}
}
netOutputStream.writeLong(value);
// </Array>
// </Data Array>
// <Block Light>
netOutputStream.writeBytes(blockLight.getRawData());
// </Block Light>
// <Sky Light>
netOutputStream.writeBytes(skyLight.getRawData());
// </Sky Light>
}
}
}

View File

@@ -15,7 +15,7 @@ public class ByteArrayInputNetStream extends NetInputStream_p340 {
@Override
public boolean readBoolean() {
throw new UnsupportedOperationException();
return readByte() != 0;
}
@Override
@@ -39,7 +39,7 @@ public class ByteArrayInputNetStream extends NetInputStream_p340 {
@Override
public int readUnsignedByte() {
throw new UnsupportedOperationException();
return bais.read() & 0xFF;
}
@Override

View File

@@ -8,6 +8,8 @@ import java.util.Random;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ByteArrayInputNetStreamTest {
private Random random;
@@ -17,6 +19,23 @@ class ByteArrayInputNetStreamTest {
random = new Random(System.currentTimeMillis());
}
@Test
void testReadBoolean() {
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
byteArrayOutputNetStream.writeBoolean(true);
ByteArrayInputNetStream byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
assertTrue(byteArrayInputNetStream.readBoolean());
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
byteArrayOutputNetStream.writeBoolean(false);
byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
assertFalse(byteArrayInputNetStream.readBoolean());
}
@Test
void testReadByte() throws IOException {
final byte[] bytes = new byte[1];
@@ -73,6 +92,23 @@ class ByteArrayInputNetStreamTest {
assertEquals(5, r);
}
@Test
void testReadUnsignedByte() {
ByteArrayOutputNetStream byteArrayOutputNetStream = new ByteArrayOutputNetStream();
byteArrayOutputNetStream.writeUnsignedByte(30);
ByteArrayInputNetStream byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
assertEquals(30, byteArrayInputNetStream.readUnsignedByte());
byteArrayOutputNetStream = new ByteArrayOutputNetStream();
byteArrayOutputNetStream.writeUnsignedByte(130);
byteArrayInputNetStream = new ByteArrayInputNetStream(byteArrayOutputNetStream.toByteArray());
assertEquals(130, byteArrayInputNetStream.readUnsignedByte());
}
@Test
void testReadInt() {
final int integerDig = random.nextInt();

View File

@@ -104,7 +104,7 @@ class ByteArrayOutputNetStreamTest {
byteArrayOutputNetStream.writeShort(smallInt);
assertArrayEquals(new byte[]{ (byte) (smallInt >>> 8),
(byte) smallInt },
(byte) smallInt },
byteArrayOutputNetStream.toByteArray());
}
@@ -116,9 +116,9 @@ class ByteArrayOutputNetStreamTest {
byteArrayOutputNetStream.writeInt(integerDig);
assertArrayEquals(new byte[]{ (byte) ((integerDig >>> 24) & 0xFF),
(byte) ((integerDig >>> 16) & 0xFF),
(byte) ((integerDig >>> 8) & 0xFF),
(byte) (integerDig & 0xFF) },
(byte) ((integerDig >>> 16) & 0xFF),
(byte) ((integerDig >>> 8) & 0xFF),
(byte) (integerDig & 0xFF) },
byteArrayOutputNetStream.toByteArray());
}
@@ -130,13 +130,13 @@ class ByteArrayOutputNetStreamTest {
byteArrayOutputNetStream.writeLong(longDig);
assertArrayEquals(new byte[]{ (byte) ((longDig >>> 56) & 0xFF),
(byte) ((longDig >>> 48) & 0xFF),
(byte) ((longDig >>> 40) & 0xFF),
(byte) ((longDig >>> 32) & 0xFF),
(byte) ((longDig >>> 24) & 0xFF),
(byte) ((longDig >>> 16) & 0xFF),
(byte) ((longDig >>> 8) & 0xFF),
(byte) (longDig & 0xFF) },
(byte) ((longDig >>> 48) & 0xFF),
(byte) ((longDig >>> 40) & 0xFF),
(byte) ((longDig >>> 32) & 0xFF),
(byte) ((longDig >>> 24) & 0xFF),
(byte) ((longDig >>> 16) & 0xFF),
(byte) ((longDig >>> 8) & 0xFF),
(byte) (longDig & 0xFF) },
byteArrayOutputNetStream.toByteArray());
}
@@ -149,9 +149,9 @@ class ByteArrayOutputNetStreamTest {
final int floatBits = Float.floatToIntBits(floatDig);
assertArrayEquals(new byte[]{ (byte) ((floatBits >>> 24) & 0xFF),
(byte) ((floatBits >>> 16) & 0xFF),
(byte) ((floatBits >>> 8) & 0xFF),
(byte) (floatBits & 0xFF) },
(byte) ((floatBits >>> 16) & 0xFF),
(byte) ((floatBits >>> 8) & 0xFF),
(byte) (floatBits & 0xFF) },
byteArrayOutputNetStream.toByteArray());
}
@@ -164,13 +164,13 @@ class ByteArrayOutputNetStreamTest {
final long doubleBits = Double.doubleToLongBits(doubleDig);
assertArrayEquals(new byte[]{ (byte) ((doubleBits >>> 56) & 0xFF),
(byte) ((doubleBits >>> 48) & 0xFF),
(byte) ((doubleBits >>> 40) & 0xFF),
(byte) ((doubleBits >>> 32) & 0xFF),
(byte) ((doubleBits >>> 24) & 0xFF),
(byte) ((doubleBits >>> 16) & 0xFF),
(byte) ((doubleBits >>> 8) & 0xFF),
(byte) (doubleBits & 0xFF) },
(byte) ((doubleBits >>> 48) & 0xFF),
(byte) ((doubleBits >>> 40) & 0xFF),
(byte) ((doubleBits >>> 32) & 0xFF),
(byte) ((doubleBits >>> 24) & 0xFF),
(byte) ((doubleBits >>> 16) & 0xFF),
(byte) ((doubleBits >>> 8) & 0xFF),
(byte) (doubleBits & 0xFF) },
byteArrayOutputNetStream.toByteArray());
}
@@ -238,22 +238,22 @@ class ByteArrayOutputNetStreamTest {
final long leastSignificantBits = uuid.getLeastSignificantBits();
assertArrayEquals(new byte[]{ (byte) ((mostSignificantBits >>> 56) & 0xFF),
(byte) ((mostSignificantBits >>> 48) & 0xFF),
(byte) ((mostSignificantBits >>> 40) & 0xFF),
(byte) ((mostSignificantBits >>> 32) & 0xFF),
(byte) ((mostSignificantBits >>> 24) & 0xFF),
(byte) ((mostSignificantBits >>> 16) & 0xFF),
(byte) ((mostSignificantBits >>> 8) & 0xFF),
(byte) (mostSignificantBits & 0xFF),
(byte) ((mostSignificantBits >>> 48) & 0xFF),
(byte) ((mostSignificantBits >>> 40) & 0xFF),
(byte) ((mostSignificantBits >>> 32) & 0xFF),
(byte) ((mostSignificantBits >>> 24) & 0xFF),
(byte) ((mostSignificantBits >>> 16) & 0xFF),
(byte) ((mostSignificantBits >>> 8) & 0xFF),
(byte) (mostSignificantBits & 0xFF),
(byte) ((leastSignificantBits >>> 56) & 0xFF),
(byte) ((leastSignificantBits >>> 48) & 0xFF),
(byte) ((leastSignificantBits >>> 40) & 0xFF),
(byte) ((leastSignificantBits >>> 32) & 0xFF),
(byte) ((leastSignificantBits >>> 24) & 0xFF),
(byte) ((leastSignificantBits >>> 16) & 0xFF),
(byte) ((leastSignificantBits >>> 8) & 0xFF),
(byte) (leastSignificantBits & 0xFF) },
(byte) ((leastSignificantBits >>> 56) & 0xFF),
(byte) ((leastSignificantBits >>> 48) & 0xFF),
(byte) ((leastSignificantBits >>> 40) & 0xFF),
(byte) ((leastSignificantBits >>> 32) & 0xFF),
(byte) ((leastSignificantBits >>> 24) & 0xFF),
(byte) ((leastSignificantBits >>> 16) & 0xFF),
(byte) ((leastSignificantBits >>> 8) & 0xFF),
(byte) (leastSignificantBits & 0xFF) },
byteArrayOutputNetStream.toByteArray());
}
}

View File

@@ -0,0 +1,312 @@
package mc.core.network.proto_1_12_2.packets;
import com.flowpowered.nbt.*;
import javafx.util.Pair;
import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream;
import mc.core.network.proto_1_12_2.packets.DumbChunkData.DumbChunkSection;
import mc.core.world.Biome;
import mc.core.world.block.*;
import mc.core.world.chunk.Chunk;
import mc.core.world.chunk.ChunkSection;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.*;
class ChunkDataPacketTest {
private static List<Pair<DumbChunkData, DumbChunkData>> listOfParams;
private static DumbChunkData createExpectedData(String xz) throws IOException {
InputStream inputStream = ChunkDataPacketTest.class.getResourceAsStream(String.format("ChunkDataPacket%s.bin", xz));
assertNotNull(inputStream);
return DumbChunkData.ReadFromNetInputStream(IOUtils.toByteArray(inputStream));
}
private static Block createChestBlock00(BlockType type, int x, int y, int z, int height) {
final BlockLocation location = new BlockLocation(x, y, z);
final CompoundMap compoundMap = new CompoundMap();
compoundMap.put(new IntTag("x", x));
compoundMap.put(new IntTag("y", (height << 4) + y));
compoundMap.put(new IntTag("z", z));
compoundMap.put(new StringTag("id", type.getNamedId()));
final CompoundTag compoundTag = new CompoundTag("", compoundMap);
return new AbstractBlock(type) {
@Override
public BlockLocation getLocation() {
return location;
}
@Override
public CompoundTag getNBTData() {
return compoundTag;
}
};
}
private static ChunkSection createChunkSection00(int height) {
final ChunkSection chunkSection = mock(ChunkSection.class);
when(chunkSection.getSkyLight(anyInt(), anyInt(), anyInt())).thenReturn(0);
when(chunkSection.getY()).thenReturn(height);
if (height == 0) {
when(chunkSection.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
final int x = (int) args[0];
final int y = (int) args[1];
final int z = (int) args[2];
BlockFactory blockFactory = new BlockFactory();
if (y == 0) {
// @formatter:off
if (x == 0 && z == 0) return blockFactory.create(BlockType.STONE, x, y, z);
else if (x == 15 && z == 0) return blockFactory.create(BlockType.GRANITE, x, y, z);
else if (x == 0 && z == 15) return blockFactory.create(BlockType.POLISHED_GRANITE, x, y, z);
else if (x == 15 && z == 15) return blockFactory.create(BlockType.DIORITE, x, y, z);
else return blockFactory.create(BlockType.BEDROCK, x, y, z);
// @formatter:on
} else {
return blockFactory.create(BlockType.STONE, x, y, z);
}
});
} else {
when(chunkSection.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
final int x = (int) args[0];
final int y = (int) args[1];
final int z = (int) args[2];
BlockFactory blockFactory = new BlockFactory();
// @formatter:off
if (y == 0) return blockFactory.create(BlockType.DIRT, x, y, z);
else if (y == 1) return blockFactory.create(BlockType.GRASS, x, y, z);
else if (y == 2) {
if ((x == 2 || x == 4 || x == 5) && z == 1)
return createChestBlock00(BlockType.CHEST_NORTH, x, y, z, height);
else if ((x == 2 || x == 3 || x == 5) && z == 6)
return createChestBlock00(BlockType.CHEST_SOUTH, x, y, z, height);
else if (x == 1 && (z == 2 || z == 3 || z == 5))
return createChestBlock00(BlockType.CHEST_WEST, x, y, z, height);
else if (x == 6 && (z == 2 || z == 4 || z == 5))
return createChestBlock00(BlockType.CHEST_EAST, x, y, z, height);
else
return blockFactory.create(BlockType.AIR, x, y, z);
}
else return blockFactory.create(BlockType.AIR, x, y, z);
// @formatter:on
});
}
return chunkSection;
}
private static Chunk createMockChunk00() {
final ChunkSection chunkSection0 = createChunkSection00(0);
final ChunkSection chunkSection1 = createChunkSection00(1);
final Chunk chunk = mock(Chunk.class);
when(chunk.getX()).thenReturn(0);
when(chunk.getZ()).thenReturn(0);
when(chunk.getBiome(anyInt(), anyInt())).thenReturn(Biome.PLAINS);
when(chunk.getChunkSection(0)).thenReturn(chunkSection0);
when(chunk.getChunkSection(1)).thenReturn(chunkSection1);
return chunk;
}
private static ChunkSection createChunkSection01() {
final ChunkSection chunkSection = mock(ChunkSection.class);
when(chunkSection.getSkyLight(anyInt(), anyInt(), anyInt())).thenReturn(0);
when(chunkSection.getY()).thenReturn(0);
final List<BlockType> types = Arrays.asList(
BlockType.CLAY,
BlockType.ORE_REDSTONE,
BlockType.ORE_DIAMOND,
BlockType.OBSIDIAN,
BlockType.STONE_MOSS,
BlockType.SANDSTONE,
BlockType.ORE_LAPIS,
BlockType.WOOD_JUNGLE,
BlockType.WOOD_BIRCH,
BlockType.WOOD_SPRUCE,
BlockType.WOOD_OAK,
BlockType.ORE_COAL,
BlockType.ORE_IRON,
BlockType.ORE_GOLD,
BlockType.GRAVEL,
BlockType.SAND
);
when(chunkSection.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
final int x = (int) args[0];
final int y = (int) args[1];
final int z = (int) args[2];
BlockFactory blockFactory = new BlockFactory();
if (y == 0) {
// @formatter:off
if (x == 0 && z == 0) return blockFactory.create(BlockType.STONE, x, y, z);
else if (x == 15 && z == 0) return blockFactory.create(BlockType.GRANITE, x, y, z);
else if (x == 0 && z == 15) return blockFactory.create(BlockType.POLISHED_GRANITE, x, y, z);
else if (x == 15 && z == 15) return blockFactory.create(BlockType.DIORITE, x, y, z);
else return blockFactory.create(BlockType.BEDROCK, x, y, z);
// @formatter:on
} else if (y == 1) {
return blockFactory.create(types.get(x), x, y, z);
} else {
return blockFactory.create(BlockType.AIR, x, y, z);
}
});
return chunkSection;
}
private static Chunk createMockChunk01() {
final ChunkSection chunkSection0 = createChunkSection01();
final Chunk chunk = mock(Chunk.class);
when(chunk.getX()).thenReturn(0);
when(chunk.getZ()).thenReturn(1);
when(chunk.getBiome(anyInt(), anyInt())).thenReturn(Biome.PLAINS);
when(chunk.getChunkSection(0)).thenReturn(chunkSection0);
return chunk;
}
private static void verifyMock(Chunk chunk) {
verify(chunk, atLeast(1)).getX();
verify(chunk, atLeast(1)).getZ();
verify(chunk, times(256)).getBiome(anyInt(), anyInt());
verify(chunk, atLeast(2)).getChunkSection(anyInt());
}
private static DumbChunkData createActualData(Chunk chunk) {
ChunkDataPacket packet = new ChunkDataPacket();
packet.setX(chunk.getX());
packet.setZ(chunk.getZ());
packet.setChunk(chunk);
packet.setInitChunk(true);
ByteArrayOutputNetStream netStream = new ByteArrayOutputNetStream();
packet.writeSelf(netStream);
verifyMock(chunk);
return DumbChunkData.ReadFromNetInputStream(netStream.toByteArray());
}
@BeforeAll
static void beforeClassTest() throws IOException {
listOfParams = Arrays.asList(
new Pair<>(createExpectedData("00"), createActualData(createMockChunk00())),
new Pair<>(createExpectedData("01"), createActualData(createMockChunk01()))
);
}
private static Stream<Arguments> streamArguments() {
return listOfParams.stream().map(pair -> Arguments.of(pair.getKey(), pair.getValue()));
}
@DisplayName("testGeneral")
@ParameterizedTest(name = "[{index}] {0}")
@MethodSource("streamArguments")
void testGeneral(DumbChunkData expected, DumbChunkData actual) {
assertEquals(expected.getX(), actual.getX(), "X coord not equals");
assertEquals(expected.getZ(), actual.getZ(), "Z coord not equals");
assertEquals(expected.isInitChunk(), actual.isInitChunk(), "Flag init chunk not equals");
assertEquals(expected.getBitMask(), actual.getBitMask(), "BitMask not equals");
assertArrayEquals(expected.getBiomes(), actual.getBiomes(), "Biomes not equals");
}
@DisplayName("testNBT")
@ParameterizedTest(name = "[{index}] {0}")
@MethodSource("streamArguments")
void testNBT(DumbChunkData expected, DumbChunkData actual) {
assertEquals(expected.getNumberNBT(), actual.getNumberNBT());
assertEquals(expected.getNbt().size(), actual.getNbt().size());
for (Tag<?> tag : actual.getNbt()) {
assertTrue(expected.getNbt().contains(tag));
}
}
@DisplayName("testData (disabled light test)")
@ParameterizedTest(name = "[{index}] {0}")
@MethodSource("streamArguments")
void testData(DumbChunkData expected, DumbChunkData actual) {
assertEquals(expected.getData().length, actual.getData().length);
for (int numberSection = 0; numberSection < expected.getData().length; numberSection++) {
final DumbChunkSection expectedSection = expected.getData()[numberSection];
final DumbChunkSection actualSection = actual.getData()[numberSection];
// Palette
testPalette(expectedSection, actualSection, numberSection);
// Data
testDataBlock(expectedSection, actualSection, numberSection);
// Block and Sky light
// DISABLE //
//testLight(expectedSection, actualSection, numberSection);
}
}
private void testPalette(DumbChunkSection expected, DumbChunkSection actual, int numberSection) {
assertEquals(expected.getBitsPerBlock(), actual.getBitsPerBlock());
if (expected.getPalette().size() > actual.getPalette().size()) {
for (int j = 0; j < actual.getPalette().size(); j++) {
assertTrue(expected.getPalette().contains(
actual.getPalette().get(j)
), String.format("[%d] Palette not contains %s", numberSection, actual.getPalette().get(j)));
}
} else {
for (int j = 0; j < expected.getPalette().size(); j++) {
assertTrue(actual.getPalette().contains(
expected.getPalette().get(j)
), String.format("[%d] Palette not contains %s", numberSection, actual.getPalette().get(j)));
}
}
}
private void testDataBlock(DumbChunkSection expected, DumbChunkSection actual, int numberSection) {
assertEquals(expected.getData().size(), actual.getData().size());
for (int j = 0; j < expected.getData().size(); j++) {
assertEquals(
expected.getData().get(j),
actual.getData().get(j),
String.format("[%d] Data (blocks)", numberSection)
);
}
}
private void testLight(DumbChunkSection expected, DumbChunkSection actual, int numberSection) {
// Block light
assertArrayEquals(expected.getBlockLight(), actual.getBlockLight(),
String.format("[%d] Block light", numberSection));
// Sky light
assertArrayEquals(expected.getSkyLight(), actual.getSkyLight(),
String.format("[%d] Sky light", numberSection));
}
}

View File

@@ -1,93 +0,0 @@
package mc.core.network.proto_1_12_2.packets;
import com.google.common.io.ByteStreams;
import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream;
import mc.core.world.Biome;
import mc.core.world.World;
import mc.core.world.WorldType;
import mc.core.world.block.BlockFactory;
import mc.core.world.block.BlockType;
import mc.core.world.chunk.Chunk;
import mc.core.world.chunk.ChunkSection;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class ChunkdataPacketTest {
private static byte[] expectedPacketData;
private World world;
@BeforeAll
static void beforeClassTest() throws IOException {
InputStream inputStream = ChunkdataPacketTest.class.getResourceAsStream("ChunkDataPacket.bin");
expectedPacketData = ByteStreams.toByteArray(inputStream);
}
@BeforeEach
void prepareWorld() {
final ChunkSection chunkSection = mock(ChunkSection.class);
when(chunkSection.getSkyLight(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
int y = (int)invocation.getArguments()[1];
if (y <= 3) return 0;
else return 15;
});
when(chunkSection.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
int x = (int) args[0];
int y = (int) args[1];
int z = (int) args[2];
BlockFactory blockFactory = new BlockFactory();
if (y == 0) return blockFactory.create(BlockType.BEDROCK, x, y, z);
else if (y >= 1 && y <= 2) return blockFactory.create(BlockType.DIRT, x, y, z);
else if (y == 3) return blockFactory.create(BlockType.GRASS, x, y, z);
else return blockFactory.create(BlockType.AIR, x, y, z);
});
world = mock(World.class);
when(world.getType()).thenReturn(WorldType.FLAT);
when(world.getChunk(anyInt(), anyInt())).thenAnswer(invocation -> {
Object[] args = invocation.getArguments();
Chunk chunk = mock(Chunk.class);
when(chunk.getX()).thenReturn((int) args[0]);
when(chunk.getZ()).thenReturn((int) args[1]);
when(chunk.getBiome(anyInt(), anyInt())).thenReturn(Biome.PLAINS);
when(chunk.getChunkSection(anyInt())).thenAnswer(invocation1 -> {
int height = (int)invocation1.getArguments()[0];
if (height < 1) return chunkSection;
else return null;
});
return chunk;
});
}
@Test
void writePacket() {
ChunkDataPacket packet = new ChunkDataPacket();
packet.setX(0);
packet.setZ(0);
packet.setChunk(world.getChunk(0, 0));
packet.setInitChunk(true);
ByteArrayOutputNetStream netStream = new ByteArrayOutputNetStream();
packet.writeSelf(netStream);
byte[] actualPacketData = netStream.toByteArray();
assertEquals(expectedPacketData.length, actualPacketData.length);
assertArrayEquals(expectedPacketData, actualPacketData);
}
}

View File

@@ -0,0 +1,133 @@
package mc.core.network.proto_1_12_2.packets;
import com.flowpowered.nbt.Tag;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import mc.core.network.proto_1_12_2.ByteArrayInputNetStream;
import mc.core.world.block.BlockType;
import java.nio.ByteBuffer;
import java.nio.LongBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
class DumbChunkData {
private int x;
private int z;
private boolean initChunk;
private int bitMask;
private int sizeOfData;
private DumbChunkSection[] data;
private byte[] biomes;
private int numberNBT;
private List<Tag<?>> nbt;
private static BlockType deserializeBlockState(int blockState) {
return BlockType.getByIdMeta(blockState >> 4, blockState & 0x0F);
}
static DumbChunkData ReadFromNetInputStream(byte[] bytes) {
ByteArrayInputNetStream netStream = new ByteArrayInputNetStream(bytes);
DumbChunkData dumbChunkData = new DumbChunkData();
dumbChunkData.x = netStream.readInt();
dumbChunkData.z = netStream.readInt();
dumbChunkData.initChunk = netStream.readBoolean();
dumbChunkData.bitMask = netStream.readVarInt();
int countOfSections = 0;
for (int shift = 0; shift < 8; shift++) {
countOfSections += ((dumbChunkData.bitMask >> shift) & 0x01) > 0 ? 1 : 0;
}
dumbChunkData.sizeOfData = netStream.readVarInt();
dumbChunkData.data = new DumbChunkSection[countOfSections];
for (int c = 0; c < countOfSections; c++) {
DumbChunkSection dumbChunkSection = new DumbChunkSection();
dumbChunkSection.bitsPerBlock = netStream.readUnsignedByte();
int sizePalette = netStream.readVarInt();
dumbChunkSection.palette = new ArrayList<>(sizePalette);
for (int i = 0; i < sizePalette; i++) {
dumbChunkSection.palette.add(deserializeBlockState(netStream.readVarInt()));
}
final byte[] rawData = new byte[netStream.readVarInt() * 8];
netStream.readBytes(rawData);
LongBuffer data = ByteBuffer.wrap(rawData).asLongBuffer();
final int bitMask = (1 << dumbChunkSection.bitsPerBlock) - 1;
dumbChunkSection.data = new ArrayList<>(4096);
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
final int blockNumber = (((y << 4) + z) << 4) + x;
final int startLong = ( blockNumber * dumbChunkSection.bitsPerBlock ) / 64;
final int startOffset = ( blockNumber * dumbChunkSection.bitsPerBlock ) % 64;
final int endLong = ((blockNumber + 1) * dumbChunkSection.bitsPerBlock - 1) / 64;
int idxBlock;
if (startLong == endLong) {
idxBlock = (int)(data.get(startLong) >> startOffset);
} else {
int endOffset = 64 - startOffset;
long mask = (1 << endOffset) - 1;
idxBlock = (int)(((data.get(startLong) >> startOffset) & mask) | data.get(endLong) << endOffset);
}
dumbChunkSection.data.add(dumbChunkSection.palette.get(idxBlock & bitMask));
}
}
}
dumbChunkSection.blockLight = new byte[2048];
netStream.readBytes(dumbChunkSection.blockLight);
dumbChunkSection.skyLight = new byte[2048];
netStream.readBytes(dumbChunkSection.skyLight);
dumbChunkData.data[c] = dumbChunkSection;
}
dumbChunkData.biomes = new byte[256];
netStream.readBytes(dumbChunkData.biomes);
dumbChunkData.numberNBT = netStream.readVarInt();
if (dumbChunkData.numberNBT > 0) {
dumbChunkData.nbt = new ArrayList<>(dumbChunkData.numberNBT);
for (int i = 0; i < dumbChunkData.numberNBT; i++) {
dumbChunkData.nbt.add(netStream.readNBT());
}
} else {
dumbChunkData.nbt = Collections.emptyList();
}
return dumbChunkData;
}
@Override
public String toString() {
return "DumbChunkData{" +
"x=" + x +
", z=" + z +
'}';
}
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
static class DumbChunkSection {
private int bitsPerBlock;
private List<BlockType> palette;
private List<BlockType> data;
private byte[] blockLight;
private byte[] skyLight;
}
}