первый удачный алгоритм загрузки мира
This commit is contained in:
@@ -2,15 +2,22 @@ package mc.protocol.packets.play.server;
|
|||||||
|
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import mc.protocol.buffer.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.packets.ServerSidePacket;
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
import mc.protocol.pool.ObjectPool;
|
|
||||||
import mc.protocol.pool.ProtocolObjectPool;
|
import mc.protocol.pool.ProtocolObjectPool;
|
||||||
|
import mc.protocol.utils.NibbleArray;
|
||||||
|
import mc.protocol.utils.PaletteChunkSection;
|
||||||
|
import mc.protocol.utils.PaletteChunkSection.PaletteCoords;
|
||||||
|
import mc.protocol.world.Block;
|
||||||
|
import mc.protocol.world.Chunk;
|
||||||
|
import mc.protocol.world.ChunkSection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Данные чанка.
|
* Данные чанка.
|
||||||
*
|
*
|
||||||
* <p>Структура пакета</p>
|
* <h2>Структура пакета</h2>
|
||||||
* <pre>
|
* <pre>
|
||||||
* | FIELD | TYPE | NOTES |
|
* | FIELD | TYPE | NOTES |
|
||||||
* |--------------------------|------------- |------------------------------------------------------------------------------------|
|
* |--------------------------|------------- |------------------------------------------------------------------------------------|
|
||||||
@@ -19,101 +26,229 @@ import mc.protocol.pool.ProtocolObjectPool;
|
|||||||
* | Is Full chunk | Boolean | См. Chunk Format |
|
* | Is Full chunk | Boolean | См. Chunk Format |
|
||||||
* | Available Sections | VarInt | Битовая маска, где каждый бит - это часть чанка (0-15) |
|
* | Available Sections | VarInt | Битовая маска, где каждый бит - это часть чанка (0-15) |
|
||||||
* | Size of Data | VarInt | Размер поля "Data" |
|
* | Size of Data | VarInt | Размер поля "Data" |
|
||||||
* | Data | Byte array | Данные чанка. См. Chunk Format |
|
* | Data | Byte array | Данные чанка. См. Data Structure |
|
||||||
* | Number of block entities | VarInt | Количество элементов в поле "Block entities" |
|
* | Number of block entities | VarInt | Количество элементов в поле "Block entities" |
|
||||||
* | Block entities | Array of NBT | Все сущности в чанке |
|
* | Block entities | Array of NBT | Все сущности в чанке |
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
|
* <h2>Data Structure</h2>
|
||||||
|
* <pre>
|
||||||
|
* | FIELD | TYPE | NOTES |
|
||||||
|
* |--------|------------------------|------------------------------------------------------------|
|
||||||
|
* | Data | Array of Chunk Section | См. Chunk Section Structure |
|
||||||
|
* | Biomes | Byte array | Optional. Отправляются только если "Is Full chunk" == true |
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <h2>Chunk Section Structure</h2>
|
||||||
|
* <pre>
|
||||||
|
* | FIELD | TYPE | NOTES |
|
||||||
|
* |-------------------|---------------|---------------------------------------------------------------------|
|
||||||
|
* | Bits Per Block | Unsigned Byte | Определяет, сколько битов используется для кодирования блока |
|
||||||
|
* | Palette | Byte array | См. Palette Structure |
|
||||||
|
* | Data Array Length | VarInt | |
|
||||||
|
* | Data Array | Array of Long | |
|
||||||
|
* | Block Light | Byte array | Половина байна на блок |
|
||||||
|
* | Sky Light | Byte array | Optional. Только для LevelType == Overworld. Половина байна на блок |
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <h2>Palette Structure</h2>
|
||||||
|
* <p>Есть два типа: Indirect и Direct.</p>
|
||||||
|
* <p>
|
||||||
|
* Indirect используется, если "Bits Per Block" < 9. При этом, если "Bits Per Block" <= 4,
|
||||||
|
* то должно использоваться значение 4.
|
||||||
|
* </p>
|
||||||
|
* <p>Для Indirect формат следующий</p>
|
||||||
|
* <pre>
|
||||||
|
* | FIELD | TYPE | NOTES |
|
||||||
|
* |----------------|-----------------|--------------------------------|
|
||||||
|
* | Palette Length | VarInt | Количество элементов в массиве |
|
||||||
|
* | Palette | Array of VarInt | Идентификаторы блоков |
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>Direct используется, если "Bits Per Block" >= 9</p>
|
||||||
|
* <p>Для Direct формат следующий</p>
|
||||||
|
* <pre>
|
||||||
|
* | FIELD | TYPE | NOTES |
|
||||||
|
* |----------------------|--------|-------------|
|
||||||
|
* | Dummy Palette Length | VarInt | Всегда == 0 |
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Chunk_Data">Chunk Data</a>
|
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Chunk_Data">Chunk Data</a>
|
||||||
* @see <a href="https://wiki.vg/index.php?title=Chunk_Format&oldid=14135">Chunk Format</a>
|
* @see <a href="https://wiki.vg/index.php?title=Chunk_Format&oldid=14135">Chunk Format</a>
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class ChunkDataPacket implements ServerSidePacket {
|
public class ChunkDataPacket implements ServerSidePacket {
|
||||||
|
|
||||||
private static NetByteBuf voidData;
|
private static final int FULL_BIT_MASK = 0b11111111_11111111;
|
||||||
|
private static final int _16_16_16 = 16 * 16 * 16;
|
||||||
|
private static final int SIZE_OF_LONG_IN_BITS = 64;
|
||||||
|
|
||||||
private int x;
|
private Chunk chunk;
|
||||||
private int z;
|
|
||||||
|
|
||||||
@SuppressWarnings("java:S125")
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSelf(NetByteBuf netByteBuf) {
|
public void writeSelf(NetByteBuf netByteBuf) {
|
||||||
netByteBuf.writeInt(x);
|
netByteBuf.writeInt(chunk.getX()); // Chunk X
|
||||||
netByteBuf.writeInt(z);
|
netByteBuf.writeInt(chunk.getZ()); // Chunk Z
|
||||||
/* Временное отключение кода
|
|
||||||
netByteBuf.writeBoolean(true); // Is Full chunk
|
|
||||||
netByteBuf.writeVarInt(0b11111111); // Available Sections
|
|
||||||
|
|
||||||
NetByteBuf data = new NetByteBuf(Unpooled.buffer());
|
AvailableSections availableSections = createAvailableSections();
|
||||||
// <Data>
|
boolean fullChunk = availableSections.getBitMask() == FULL_BIT_MASK;
|
||||||
for (int i = 0; i < 16; i++) {
|
netByteBuf.writeBoolean(fullChunk); // Is Full chunk
|
||||||
NetByteBuf dataBuff = new NetByteBuf(Unpooled.wrappedBuffer(new byte[4096]));
|
netByteBuf.writeVarInt(availableSections.getBitMask()); // Available Sections
|
||||||
NetByteBuf blockLight = new NetByteBuf(Unpooled.wrappedBuffer(new byte[2048]));
|
|
||||||
NetByteBuf skyLight = new NetByteBuf(Unpooled.wrappedBuffer(new byte[2048]));
|
|
||||||
NetByteBuf biomes = new NetByteBuf(Unpooled.wrappedBuffer(new byte[256]));
|
|
||||||
|
|
||||||
// <Chunk Section>
|
|
||||||
data.writeUnsignedByte(13); // Bits Per Block
|
|
||||||
// <Palette>
|
|
||||||
data.writeUnsignedByte(0); // Palette Length (for direct)
|
|
||||||
// <Palette Data/>
|
|
||||||
// </Palette>
|
|
||||||
data.writeVarInt(dataBuff.readableBytes()); // Data Array Length
|
|
||||||
data.writeBytes(dataBuff); // Data Array
|
|
||||||
data.writeBytes(blockLight); // Block Light
|
|
||||||
data.writeBytes(skyLight); // Sky Light
|
|
||||||
// </Chunk Section>
|
|
||||||
data.writeBytes(biomes); // Biomes
|
|
||||||
}
|
|
||||||
// </Data>
|
|
||||||
|
|
||||||
|
NetByteBuf data = createDataStructure(availableSections.getMaxHeight(), fullChunk);
|
||||||
netByteBuf.writeVarInt(data.readableBytes()); // Size of Data
|
netByteBuf.writeVarInt(data.readableBytes()); // Size of Data
|
||||||
netByteBuf.writeBytes(data); // Data
|
netByteBuf.writeBytes(data); // Data
|
||||||
|
|
||||||
netByteBuf.writeVarInt(0); // Number of block entities
|
netByteBuf.writeVarInt(0); // Number of block entities
|
||||||
// write NBT's
|
// Block entities (NBT's)
|
||||||
*/
|
|
||||||
|
|
||||||
netByteBuf.writeBytes(voidData);
|
|
||||||
|
|
||||||
voidData.resetReaderIndex();
|
|
||||||
voidData.resetWriterIndex();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
private AvailableSections createAvailableSections() {
|
||||||
ObjectPool<NetByteBuf> pool = ProtocolObjectPool.getNetByteBufPool();
|
int bitMask = 0;
|
||||||
|
int maxH = 0;
|
||||||
voidData = pool.borrowObject().setByteBuf(Unpooled.buffer());
|
for (int h = 15; h >= 0; h--) {
|
||||||
voidData.writeBoolean(true); // Is Full chunk
|
bitMask = bitMask << 1;
|
||||||
voidData.writeVarInt(0b11111111); // Available Sections
|
ChunkSection chunkSection = chunk.getSection(h);
|
||||||
|
if (chunkSection != null && chunkSection.getY() == h) {
|
||||||
NetByteBuf data = pool.borrowObject().setByteBuf(Unpooled.buffer());
|
bitMask |= 0x01;
|
||||||
for (int i = 0; i < 16; i++) {
|
maxH++;
|
||||||
NetByteBuf dataBuff = pool.borrowObject().setByteBuf(Unpooled.wrappedBuffer(new byte[4096]));
|
} else {
|
||||||
NetByteBuf blockLight = pool.borrowObject().setByteBuf(Unpooled.wrappedBuffer(new byte[2048]));
|
bitMask |= 0x00;
|
||||||
NetByteBuf skyLight = pool.borrowObject().setByteBuf(Unpooled.wrappedBuffer(new byte[2048]));
|
}
|
||||||
NetByteBuf biomes = pool.borrowObject().setByteBuf(Unpooled.wrappedBuffer(new byte[256]));
|
|
||||||
|
|
||||||
data.writeUnsignedByte(13);
|
|
||||||
data.writeUnsignedByte(0);
|
|
||||||
data.writeVarInt(dataBuff.readableBytes());
|
|
||||||
data.writeBytes(dataBuff);
|
|
||||||
data.writeBytes(blockLight);
|
|
||||||
data.writeBytes(skyLight);
|
|
||||||
data.writeBytes(biomes);
|
|
||||||
|
|
||||||
pool.returnObject(biomes);
|
|
||||||
pool.returnObject(skyLight);
|
|
||||||
pool.returnObject(blockLight);
|
|
||||||
pool.returnObject(dataBuff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
voidData.writeVarInt(data.readableBytes());
|
return new AvailableSections(bitMask, maxH);
|
||||||
voidData.writeBytes(data);
|
}
|
||||||
voidData.writeVarInt(0);
|
|
||||||
|
|
||||||
voidData.markReaderIndex();
|
private NetByteBuf createDataStructure(int maxHeight, boolean fillBiomes) {
|
||||||
voidData.markWriterIndex();
|
// NetByteBuf dataStructure = ProtocolObjectPool.getNetByteBufPool().borrowObject().setByteBuf(Unpooled.buffer());
|
||||||
|
NetByteBuf dataStructure = new NetByteBuf().setByteBuf(Unpooled.buffer());
|
||||||
|
// NetByteBuf biomes = fillBiomes ? ProtocolObjectPool.getNetByteBufPool().borrowObject().setByteBuf(Unpooled.buffer()) : null;
|
||||||
|
NetByteBuf biomes = fillBiomes ? new NetByteBuf().setByteBuf(Unpooled.buffer()) : null;
|
||||||
|
|
||||||
pool.returnObject(data);
|
for (int h = 0; h < maxHeight; h++) {
|
||||||
|
ChunkSection section = chunk.getSection(h);
|
||||||
|
if (section == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
dataStructure.writeBytes(createData(section, biomes)); // Data
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fillBiomes) {
|
||||||
|
dataStructure.writeBytes(biomes); // Biomes
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataStructure;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetByteBuf createData(ChunkSection section, NetByteBuf biomes) {
|
||||||
|
// NetByteBuf data = ProtocolObjectPool.getNetByteBufPool().borrowObject().setByteBuf(Unpooled.buffer());
|
||||||
|
NetByteBuf data = new NetByteBuf().setByteBuf(Unpooled.buffer());
|
||||||
|
|
||||||
|
PaletteChunkSection paletteSection = new PaletteChunkSection();
|
||||||
|
NibbleArray blockLight = new NibbleArray();
|
||||||
|
NibbleArray skyLight = new NibbleArray();
|
||||||
|
fillPalette(section, paletteSection, blockLight, skyLight);
|
||||||
|
|
||||||
|
// <Bits Per Block>
|
||||||
|
int bitsPerBlock = paletteSection.bitsPerBlock();
|
||||||
|
data.writeUnsignedByte(bitsPerBlock);
|
||||||
|
// </Bits Per Block>
|
||||||
|
|
||||||
|
// <Palette>
|
||||||
|
paletteSection.writePalette(data);
|
||||||
|
// </Palette>
|
||||||
|
|
||||||
|
// <Data Array Length>
|
||||||
|
int dataLength = (_16_16_16 * bitsPerBlock) / SIZE_OF_LONG_IN_BITS;
|
||||||
|
data.writeVarInt(dataLength);
|
||||||
|
// </Data Array Length>
|
||||||
|
|
||||||
|
// <Data Array>
|
||||||
|
//TODO алгоритм побитовой записи в long вынести в utils
|
||||||
|
// Возможно даже пораднив с NibbleArray
|
||||||
|
int lastPos = 0;
|
||||||
|
long value = 0;
|
||||||
|
boolean fairy = false;
|
||||||
|
long fairyValue = 0;
|
||||||
|
boolean writeBiomes = biomes != null;
|
||||||
|
|
||||||
|
for (int y = 0; y < 16; y++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
//@formatter:off
|
||||||
|
int blockNumber = (((y << 4) + z) << 4) + x;
|
||||||
|
int startLong = ( blockNumber * bitsPerBlock ) / SIZE_OF_LONG_IN_BITS;
|
||||||
|
int startOffset = ( blockNumber * bitsPerBlock ) % SIZE_OF_LONG_IN_BITS;
|
||||||
|
int endLong = ((blockNumber + 1) * bitsPerBlock - 1) / SIZE_OF_LONG_IN_BITS;
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
long idxBlockInPalette = paletteSection.getIndexBlockInPalette(x, y, z);
|
||||||
|
|
||||||
|
if (startLong != lastPos) {
|
||||||
|
data.writeLong(value);
|
||||||
|
lastPos = startLong;
|
||||||
|
if (fairy) {
|
||||||
|
value = fairyValue;
|
||||||
|
fairy = false;
|
||||||
|
} else {
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value |= (idxBlockInPalette << startOffset);
|
||||||
|
|
||||||
|
if (startLong != endLong) {
|
||||||
|
fairyValue = idxBlockInPalette >> (SIZE_OF_LONG_IN_BITS - startOffset);
|
||||||
|
fairy = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (writeBiomes) {
|
||||||
|
biomes.writeByte(chunk.getBiome(
|
||||||
|
(chunk.getX() << 4) + x,
|
||||||
|
(chunk.getZ() << 4) + z));
|
||||||
|
|
||||||
|
if (x == 15 && z == 15) {
|
||||||
|
writeBiomes = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.writeLong(value);
|
||||||
|
// </Data Array>
|
||||||
|
|
||||||
|
// <Block Light>
|
||||||
|
data.writeBytes(blockLight.getRawData());
|
||||||
|
// </Block Light>
|
||||||
|
|
||||||
|
// <Sky Light>
|
||||||
|
data.writeBytes(skyLight.getRawData());
|
||||||
|
// </Sky Light>
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillPalette(ChunkSection section, PaletteChunkSection paletteSection, NibbleArray blockLight, NibbleArray skyLight) {
|
||||||
|
for (int y = 0; y < 16; y++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
Block block = section.getBlock(x, y, z);
|
||||||
|
PaletteCoords paletteCoords = PaletteCoords.createByBlock(block);
|
||||||
|
|
||||||
|
paletteSection.addBlock(x, y, z, block);
|
||||||
|
blockLight.set(paletteCoords.getX(), paletteCoords.getY(), paletteCoords.getZ(), block.getLight());
|
||||||
|
skyLight.set(paletteCoords.getX(), paletteCoords.getY(), paletteCoords.getZ(), section.getSkyLight(x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
private static class AvailableSections {
|
||||||
|
private final int bitMask;
|
||||||
|
private final int maxHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
56
protocol/src/main/java/mc/protocol/utils/NibbleArray.java
Normal file
56
protocol/src/main/java/mc/protocol/utils/NibbleArray.java
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package mc.protocol.utils;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class NibbleArray {
|
||||||
|
|
||||||
|
private final byte[] data;
|
||||||
|
|
||||||
|
public NibbleArray(int capacity) {
|
||||||
|
this.data = new byte[capacity];
|
||||||
|
}
|
||||||
|
|
||||||
|
public NibbleArray() {
|
||||||
|
this(2048);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get(int x, int y, int z) {
|
||||||
|
int idx = coordsToIndex(x, y, z);
|
||||||
|
|
||||||
|
int ni = nibbleIndex(idx);
|
||||||
|
return isLowerNibble(idx) ? this.data[ni] & 0x0F : this.data[ni] >> 4 & 0x0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int x, int y, int z, int value) {
|
||||||
|
//@formatter:off
|
||||||
|
if (value < 0) value = 0;
|
||||||
|
else if (value > 15) value = 15;
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
int idx = coordsToIndex(x, y, z);
|
||||||
|
int ni = nibbleIndex(idx);
|
||||||
|
|
||||||
|
if (isLowerNibble(idx)) {
|
||||||
|
this.data[ni] = (byte) (value);
|
||||||
|
} else {
|
||||||
|
this.data[ni] = (byte) (this.data[ni] | value << 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getRawData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int coordsToIndex(int x, int y, int z) {
|
||||||
|
return y << 8 | z << 4 | x;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int nibbleIndex(int index) {
|
||||||
|
return index >> 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLowerNibble(int index) {
|
||||||
|
return (index & 1) == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package mc.protocol.utils;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
|
import mc.protocol.world.Block;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PaletteChunkSection {
|
||||||
|
|
||||||
|
private final byte[] blocks = new byte[4096];
|
||||||
|
private final List<Integer> palette = new ArrayList<>();
|
||||||
|
|
||||||
|
public void addBlock(int x, int y, int z, Block block) {
|
||||||
|
blocks[coordsToIndex(x, y, z)] = putPalette(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int bitsPerBlock() {
|
||||||
|
if (palette.size() <= 15) {
|
||||||
|
return 4;
|
||||||
|
} else if (palette.size() <= 31) {
|
||||||
|
return 5;
|
||||||
|
} else if (palette.size() <= 63) {
|
||||||
|
return 6;
|
||||||
|
} else if (palette.size() <= 127) {
|
||||||
|
return 7;
|
||||||
|
} else if (palette.size() <= 255) {
|
||||||
|
return 8;
|
||||||
|
} else {
|
||||||
|
return 13;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writePalette(NetByteBuf netByteBuf) {
|
||||||
|
netByteBuf.writeVarInt(palette.size()); // Size of palette
|
||||||
|
palette.forEach(netByteBuf::writeVarInt); // Palette
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getIndexBlockInPalette(int x, int y, int z) {
|
||||||
|
return blocks[coordsToIndex(x, y, z)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private int coordsToIndex(int x, int y, int z) {
|
||||||
|
return y << 8 | z << 4 | x;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte putPalette(Block block) {
|
||||||
|
int blockState = (block.getId() << 4) | block.getMeta();
|
||||||
|
|
||||||
|
int idx = palette.indexOf(blockState);
|
||||||
|
if (idx == -1) {
|
||||||
|
palette.add(blockState);
|
||||||
|
idx = palette.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (byte) idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@Getter
|
||||||
|
public static class PaletteCoords {
|
||||||
|
private final int x;
|
||||||
|
private final int y;
|
||||||
|
private final int z;
|
||||||
|
|
||||||
|
public static PaletteCoords createByBlock(Block block) {
|
||||||
|
int bx = (int) block.getLocation().getX() - (((int) block.getLocation().getX() >> 4) << 4);
|
||||||
|
int by = (int) block.getLocation().getY() - (((int) block.getLocation().getY() >> 4) << 4);
|
||||||
|
int bz = (int) block.getLocation().getZ() - (((int) block.getLocation().getZ() >> 4) << 4);
|
||||||
|
|
||||||
|
return new PaletteCoords(bx, by, bz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
protocol/src/main/java/mc/protocol/world/Block.java
Normal file
14
protocol/src/main/java/mc/protocol/world/Block.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package mc.protocol.world;
|
||||||
|
|
||||||
|
import mc.protocol.model.Location;
|
||||||
|
|
||||||
|
public interface Block {
|
||||||
|
|
||||||
|
int getId();
|
||||||
|
int getMeta();
|
||||||
|
|
||||||
|
Location getLocation();
|
||||||
|
|
||||||
|
int getLight();
|
||||||
|
void setLight(int value);
|
||||||
|
}
|
||||||
@@ -4,4 +4,7 @@ public interface Chunk {
|
|||||||
|
|
||||||
int getX();
|
int getX();
|
||||||
int getZ();
|
int getZ();
|
||||||
|
|
||||||
|
ChunkSection getSection(int index);
|
||||||
|
byte getBiome(int x, int z);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package mc.protocol.world;
|
||||||
|
|
||||||
|
public interface ChunkSection {
|
||||||
|
|
||||||
|
int getY();
|
||||||
|
|
||||||
|
Block getBlock(int x, int y, int z);
|
||||||
|
int getSkyLight(int x, int y, int z);
|
||||||
|
}
|
||||||
@@ -99,8 +99,7 @@ public class ProcessorLogin implements PacketProcessor {
|
|||||||
Chunk chunk = world.getChunk((int) chunkLocation.getX(), (int) chunkLocation.getZ());
|
Chunk chunk = world.getChunk((int) chunkLocation.getX(), (int) chunkLocation.getZ());
|
||||||
|
|
||||||
var chunkDataPacket = new ChunkDataPacket();
|
var chunkDataPacket = new ChunkDataPacket();
|
||||||
chunkDataPacket.setX(chunk.getX());
|
chunkDataPacket.setChunk(chunk);
|
||||||
chunkDataPacket.setZ(chunk.getZ());
|
|
||||||
|
|
||||||
player.getCtx().write(chunkDataPacket);
|
player.getCtx().write(chunkDataPacket);
|
||||||
|
|
||||||
@@ -113,8 +112,7 @@ public class ProcessorLogin implements PacketProcessor {
|
|||||||
for (int x = minX; x <= maxX; x++) {
|
for (int x = minX; x <= maxX; x++) {
|
||||||
if ((z == minZ || z == maxZ) || (x == minX || x == maxX)) {
|
if ((z == minZ || z == maxZ) || (x == minX || x == maxX)) {
|
||||||
chunkDataPacket = new ChunkDataPacket();
|
chunkDataPacket = new ChunkDataPacket();
|
||||||
chunkDataPacket.setX(x);
|
chunkDataPacket.setChunk(world.getChunk(x, z));
|
||||||
chunkDataPacket.setZ(z);
|
|
||||||
player.getCtx().write(chunkDataPacket);
|
player.getCtx().write(chunkDataPacket);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
server/src/main/java/mc/server/world/AirBlock.java
Normal file
33
server/src/main/java/mc/server/world/AirBlock.java
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package mc.server.world;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import mc.protocol.model.Location;
|
||||||
|
import mc.protocol.world.Block;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AirBlock implements Block {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final Location location;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMeta() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLight() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLight(int value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,25 @@ package mc.server.world;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import mc.protocol.world.Chunk;
|
import mc.protocol.world.Chunk;
|
||||||
|
import mc.protocol.world.ChunkSection;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class VoidChunk implements Chunk {
|
public class VoidChunk implements Chunk {
|
||||||
|
|
||||||
private final int x;
|
private final int x;
|
||||||
private final int z;
|
private final int z;
|
||||||
|
private final Map<Integer, VoidChunkSection> sections = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChunkSection getSection(int height) {
|
||||||
|
return sections.computeIfAbsent(height, VoidChunkSection::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte getBiome(int x, int z) {
|
||||||
|
return 127; // 127 | 7F | minecraft:void | The Void
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
29
server/src/main/java/mc/server/world/VoidChunkSection.java
Normal file
29
server/src/main/java/mc/server/world/VoidChunkSection.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package mc.server.world;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import mc.protocol.model.Location;
|
||||||
|
import mc.protocol.world.Block;
|
||||||
|
import mc.protocol.world.ChunkSection;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public class VoidChunkSection implements ChunkSection {
|
||||||
|
|
||||||
|
private final int y;
|
||||||
|
private final Map<Location, AirBlock> blocks = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Block getBlock(int x, int y, int z) {
|
||||||
|
return blocks.computeIfAbsent(new Location().set(x, y, z), AirBlock::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSkyLight(int x, int y, int z) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
package mc.server.world;
|
package mc.server.world;
|
||||||
|
|
||||||
import mc.protocol.model.Location;
|
import mc.protocol.model.Location;
|
||||||
|
import mc.protocol.pool.ProtocolObjectPool;
|
||||||
import mc.protocol.utils.LevelType;
|
import mc.protocol.utils.LevelType;
|
||||||
|
import mc.protocol.utils.Table;
|
||||||
import mc.protocol.world.Chunk;
|
import mc.protocol.world.Chunk;
|
||||||
import mc.protocol.world.World;
|
import mc.protocol.world.World;
|
||||||
|
|
||||||
public class VoidWorld implements World {
|
public class VoidWorld implements World {
|
||||||
|
|
||||||
private static final Location spawn = new Location().set(7d, 130d, 7d);
|
private static final Location spawn = ProtocolObjectPool.getLocationPool().borrowObject().set(7d, 130d, 7d);
|
||||||
|
private final Table<Integer, Integer, VoidChunk> chunkTable = new Table<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LevelType getLevelType() {
|
public LevelType getLevelType() {
|
||||||
@@ -21,6 +24,12 @@ public class VoidWorld implements World {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Chunk getChunk(int x, int z) {
|
public Chunk getChunk(int x, int z) {
|
||||||
return new VoidChunk(x, z);
|
VoidChunk chunk = chunkTable.getColumnAndRow(x, z);
|
||||||
|
if (chunk == null) {
|
||||||
|
chunk = new VoidChunk(x, z);
|
||||||
|
chunkTable.put(x, z, chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user