forked from GeyserMC/Geyser
Start work on chunks
- There are probably many errors and issues with this code (some dumb ones probably). As of right now it crashes the client.
This commit is contained in:
parent
bf8da2a3ad
commit
1c1785ac51
18 changed files with 10934 additions and 9649 deletions
|
@ -72,15 +72,9 @@
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.nukkitx.protocol</groupId>
|
<groupId>com.nukkitx</groupId>
|
||||||
<artifactId>bedrock-v354</artifactId>
|
<artifactId>fastutil-lite</artifactId>
|
||||||
<version>2.1.2</version>
|
<version>8.1.1</version>
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.nukkitx.protocol</groupId>
|
|
||||||
<artifactId>bedrock-v340</artifactId>
|
|
||||||
<version>2.1.2</version>
|
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -43,7 +43,6 @@ import org.geysermc.connector.console.GeyserLogger;
|
||||||
import org.geysermc.connector.metrics.Metrics;
|
import org.geysermc.connector.metrics.Metrics;
|
||||||
import org.geysermc.connector.network.ConnectorServerEventHandler;
|
import org.geysermc.connector.network.ConnectorServerEventHandler;
|
||||||
import org.geysermc.connector.network.remote.RemoteJavaServer;
|
import org.geysermc.connector.network.remote.RemoteJavaServer;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
|
||||||
import org.geysermc.connector.network.translators.TranslatorsInit;
|
import org.geysermc.connector.network.translators.TranslatorsInit;
|
||||||
import org.geysermc.connector.plugin.GeyserPluginLoader;
|
import org.geysermc.connector.plugin.GeyserPluginLoader;
|
||||||
import org.geysermc.connector.plugin.GeyserPluginManager;
|
import org.geysermc.connector.plugin.GeyserPluginManager;
|
||||||
|
|
|
@ -27,8 +27,10 @@ package org.geysermc.connector.entity.attribute;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@Setter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class Attribute {
|
public class Attribute {
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,9 @@ public class ItemTranslator {
|
||||||
|
|
||||||
public static BedrockItem getBedrockBlock(BlockState stack) {
|
public static BedrockItem getBedrockBlock(BlockState stack) {
|
||||||
Map<String, Object> m = Remapper.JAVA_TO_BEDROCK_BLOCKS.get(stack.getId());
|
Map<String, Object> m = Remapper.JAVA_TO_BEDROCK_BLOCKS.get(stack.getId());
|
||||||
|
if (m == null)
|
||||||
|
return BedrockItem.AIR;
|
||||||
|
|
||||||
return new BedrockItem((String) m.get("name"), (Integer) m.get("id"), (Integer) m.get("data"));
|
return new BedrockItem((String) m.get("name"), (Integer) m.get("id"), (Integer) m.get("data"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,55 @@
|
||||||
package org.geysermc.connector.network.translators.java.world;
|
package org.geysermc.connector.network.translators.java.world;
|
||||||
|
|
||||||
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket;
|
import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket;
|
||||||
import com.github.steveice10.packetlib.packet.Packet;
|
|
||||||
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
|
import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
import org.geysermc.connector.console.GeyserLogger;
|
import org.geysermc.connector.console.GeyserLogger;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
import org.geysermc.connector.network.translators.PacketTranslator;
|
import org.geysermc.connector.network.translators.PacketTranslator;
|
||||||
import org.geysermc.connector.utils.Chunks;
|
import org.geysermc.connector.utils.ChunkUtils;
|
||||||
|
import org.geysermc.connector.world.chunk.ChunkSection;
|
||||||
|
|
||||||
public class JavaChunkDataPacket extends PacketTranslator<ServerChunkDataPacket> {
|
public class JavaChunkDataPacket extends PacketTranslator<ServerChunkDataPacket> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void translate(ServerChunkDataPacket packet, GeyserSession session) {
|
public void translate(ServerChunkDataPacket packet, GeyserSession session) {
|
||||||
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
|
|
||||||
|
|
||||||
Chunks.ChunkData data = Chunks.getData(packet.getColumn());
|
try {
|
||||||
levelChunkPacket.setSubChunksLength(data.count);
|
byte[] buffer = new byte[32];
|
||||||
levelChunkPacket.setData(data.bytes);
|
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn());
|
||||||
levelChunkPacket.setChunkX(packet.getColumn().getX());
|
|
||||||
levelChunkPacket.setChunkZ(packet.getColumn().getZ());
|
int count = 0;
|
||||||
|
ChunkSection[] sections = chunkData.sections;
|
||||||
|
for (int i = sections.length - 1; i >= 0; i--) {
|
||||||
|
if (sections[i].isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
count = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
ChunkUtils.putBytes(count, buffer, new byte[]{(byte) 0});
|
||||||
|
ChunkSection section = chunkData.sections[i];
|
||||||
|
|
||||||
|
ByteBuf byteBuf = Unpooled.buffer();
|
||||||
|
section.writeToNetwork(byteBuf);
|
||||||
|
byte[] byteData = byteBuf.array();
|
||||||
|
ChunkUtils.putBytes(count, buffer, byteData);
|
||||||
|
}
|
||||||
|
|
||||||
|
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
|
||||||
|
levelChunkPacket.setSubChunksLength(16);
|
||||||
|
levelChunkPacket.setCachingEnabled(true);
|
||||||
|
levelChunkPacket.setChunkX(packet.getColumn().getX());
|
||||||
|
levelChunkPacket.setChunkZ(packet.getColumn().getZ());
|
||||||
|
levelChunkPacket.setData(buffer);
|
||||||
|
session.getUpstream().sendPacket(levelChunkPacket);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
GeyserLogger.DEFAULT.info("Sent chunk packet!");
|
GeyserLogger.DEFAULT.info("Sent chunk packet!");
|
||||||
session.getUpstream().sendPacket(levelChunkPacket);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
package org.geysermc.connector.utils;
|
||||||
|
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.chunk.BlockStorage;
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
|
||||||
|
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
|
||||||
|
import org.geysermc.connector.network.translators.TranslatorsInit;
|
||||||
|
import org.geysermc.connector.network.translators.item.BedrockItem;
|
||||||
|
import org.geysermc.connector.world.chunk.ChunkSection;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class ChunkUtils {
|
||||||
|
|
||||||
|
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
|
||||||
|
|
||||||
|
public static ChunkData translateToBedrock(Column column) {
|
||||||
|
ChunkData chunkData = new ChunkData();
|
||||||
|
chunkData.sections = new ChunkSection[16];
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
chunkData.sections[i] = new ChunkSection();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int y = 0; y < 256; y++) {
|
||||||
|
int chunkY = y >> 4;
|
||||||
|
|
||||||
|
Chunk chunk = null;
|
||||||
|
try {
|
||||||
|
chunk = column.getChunks()[chunkY];
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk == null || chunk.isEmpty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
BlockStorage storage = chunk.getBlocks();
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
BlockState block = storage.get(x, y & 0xF, z);
|
||||||
|
BedrockItem bedrockBlock = TranslatorsInit.getItemTranslator().getBedrockBlock(block);
|
||||||
|
|
||||||
|
ChunkSection section = chunkData.sections[chunkY];
|
||||||
|
|
||||||
|
org.geysermc.connector.world.chunk.BlockStorage blockStorage = new org.geysermc.connector.world.chunk.BlockStorage();
|
||||||
|
blockStorage.setFullBlock(ChunkSection.blockPosition(x, y, z), bedrockBlock.getId());
|
||||||
|
|
||||||
|
section.getBlockStorageArray()[0] = blockStorage;
|
||||||
|
section.getBlockStorageArray()[1] = blockStorage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunkData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ChunkData {
|
||||||
|
public ChunkSection[] sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void putBytes(int count, byte[] buffer, byte[] bytes) {
|
||||||
|
if (bytes == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minCapacity = count + bytes.length;
|
||||||
|
if ((minCapacity) - buffer.length > 0) {
|
||||||
|
int oldCapacity = buffer.length;
|
||||||
|
int newCapacity = oldCapacity << 1;
|
||||||
|
|
||||||
|
if (newCapacity - minCapacity < 0) {
|
||||||
|
newCapacity = minCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newCapacity - MAX_ARRAY_SIZE > 0) {
|
||||||
|
newCapacity = hugeCapacity(minCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = Arrays.copyOf(buffer, newCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.arraycopy(bytes, 0, buffer, count, bytes.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int hugeCapacity(int minCapacity) {
|
||||||
|
if (minCapacity < 0) { // overflow
|
||||||
|
throw new OutOfMemoryError();
|
||||||
|
}
|
||||||
|
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,81 +0,0 @@
|
||||||
package org.geysermc.connector.utils;
|
|
||||||
|
|
||||||
import com.github.steveice10.mc.protocol.data.game.chunk.Chunk;
|
|
||||||
import com.github.steveice10.mc.protocol.data.game.chunk.Column;
|
|
||||||
import gnu.trove.list.TByteList;
|
|
||||||
import gnu.trove.list.array.TByteArrayList;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.geysermc.connector.network.translators.item.ItemTranslator;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class Chunks {
|
|
||||||
|
|
||||||
public static ChunkData getData(Column c) {
|
|
||||||
Objects.requireNonNull(c);
|
|
||||||
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
for(Chunk chunk : c.getChunks()) {
|
|
||||||
if(chunk != null) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int block = 0;
|
|
||||||
|
|
||||||
TByteList list = new TByteArrayList(4096 * 4);
|
|
||||||
|
|
||||||
for(int i = 0; i < 256; i++) {
|
|
||||||
list.add((byte) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(Chunk chunk : c.getChunks()) {
|
|
||||||
if (chunk != null) {
|
|
||||||
list.add((byte) 0);
|
|
||||||
for (int x = 0; x < 16; x++) {
|
|
||||||
for (int y = 0; x < 16; x++) {
|
|
||||||
for (int z = 0; x < 16; x++) {
|
|
||||||
try {
|
|
||||||
list.add((byte) ItemTranslator.getBedrockBlock(chunk.getBlocks().get(x, y, z)).getId());
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
list.add((byte) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
block++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int x = 0; x < 16; x++) {
|
|
||||||
for (int y = 0; x < 16; x++) {
|
|
||||||
for (int z = 0; x < 16; x++) {
|
|
||||||
try {
|
|
||||||
list.add((byte) ItemTranslator.getBedrockBlock(chunk.getBlocks().get(x, y, z)).getData());
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
list.add((byte) 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
block++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
list.add((byte) 0);
|
|
||||||
list.add((byte) 0);
|
|
||||||
|
|
||||||
return new ChunkData(count, list.toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
@AllArgsConstructor(access = AccessLevel.PACKAGE)
|
|
||||||
public static final class ChunkData {
|
|
||||||
public final int count;
|
|
||||||
|
|
||||||
public final byte[] bytes;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.geysermc.connector.utils;
|
||||||
|
|
||||||
|
public class MathUtils {
|
||||||
|
|
||||||
|
public static int ceil(float floatNumber) {
|
||||||
|
int truncated = (int) floatNumber;
|
||||||
|
return floatNumber > truncated ? truncated + 1 : truncated;
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import org.geysermc.connector.network.translators.item.BedrockItem;
|
import org.geysermc.connector.network.translators.item.BedrockItem;
|
||||||
import org.geysermc.connector.network.translators.item.JavaItem;
|
import org.geysermc.connector.network.translators.item.JavaItem;
|
||||||
|
import org.geysermc.connector.world.GlobalBlockPalette;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
@ -38,6 +39,7 @@ public class Toolbox {
|
||||||
ByteBuf b = Unpooled.buffer();
|
ByteBuf b = Unpooled.buffer();
|
||||||
VarInts.writeUnsignedInt(b, entries.size());
|
VarInts.writeUnsignedInt(b, entries.size());
|
||||||
for (Map<String, Object> e : entries) {
|
for (Map<String, Object> e : entries) {
|
||||||
|
GlobalBlockPalette.registerMapping((int) e.get("id"));
|
||||||
BedrockUtils.writeString(b, (String) e.get("name"));
|
BedrockUtils.writeString(b, (String) e.get("name"));
|
||||||
b.writeShortLE((int) e.get("data"));
|
b.writeShortLE((int) e.get("data"));
|
||||||
b.writeShortLE((int) e.get("id"));
|
b.writeShortLE((int) e.get("id"));
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.geysermc.connector.world;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
|
||||||
|
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
|
||||||
|
*/
|
||||||
|
public class GlobalBlockPalette {
|
||||||
|
|
||||||
|
private static final Int2IntArrayMap legacyToRuntimeId = new Int2IntArrayMap();
|
||||||
|
private static final Int2IntArrayMap runtimeIdToLegacy = new Int2IntArrayMap();
|
||||||
|
private static final AtomicInteger runtimeIdAllocator = new AtomicInteger(0);
|
||||||
|
|
||||||
|
static {
|
||||||
|
legacyToRuntimeId.defaultReturnValue(-1);
|
||||||
|
runtimeIdToLegacy.defaultReturnValue(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getOrCreateRuntimeId(int id, int meta) {
|
||||||
|
return getOrCreateRuntimeId((id << 4) | meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getOrCreateRuntimeId(int legacyId) throws NoSuchElementException {
|
||||||
|
int runtimeId = legacyToRuntimeId.get(legacyId);
|
||||||
|
if (!legacyToRuntimeId.containsKey(legacyId) || runtimeId == -1) {
|
||||||
|
//runtimeId = registerMapping(runtimeIdAllocator.incrementAndGet(), legacyId);
|
||||||
|
// throw new NoSuchElementException("Unmapped block registered id:" + (legacyId >>> 4) + " meta:" + (legacyId & 0xf));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return runtimeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int registerMapping(int legacyId) {
|
||||||
|
int runtimeId = runtimeIdAllocator.getAndIncrement();
|
||||||
|
runtimeIdToLegacy.put(runtimeId, legacyId);
|
||||||
|
legacyToRuntimeId.put(legacyId, runtimeId);
|
||||||
|
return runtimeId;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package org.geysermc.connector.world.chunk;
|
||||||
|
|
||||||
|
import com.nukkitx.network.VarInts;
|
||||||
|
import gnu.trove.list.array.TIntArrayList;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import org.geysermc.connector.world.GlobalBlockPalette;
|
||||||
|
import org.geysermc.connector.world.chunk.palette.Palette;
|
||||||
|
import org.geysermc.connector.world.chunk.palette.PaletteVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
|
||||||
|
*/
|
||||||
|
public class BlockStorage {
|
||||||
|
|
||||||
|
private static final int SIZE = 4096;
|
||||||
|
|
||||||
|
private final TIntArrayList ids;
|
||||||
|
private Palette palette;
|
||||||
|
|
||||||
|
public BlockStorage() {
|
||||||
|
this(PaletteVersion.V2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockStorage(PaletteVersion version) {
|
||||||
|
this.palette = version.createPalette(SIZE);
|
||||||
|
this.ids = new TIntArrayList(16, -1);
|
||||||
|
this.ids.add(0); // Air is at the start of every palette.
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockStorage(Palette palette, TIntArrayList ids) {
|
||||||
|
this.ids = ids;
|
||||||
|
this.palette = palette;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int getFullBlock(int xzy) {
|
||||||
|
return this.palette.get(xzy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setFullBlock(int index, int legacyId) {
|
||||||
|
this.palette.set(index, this.idFor(legacyId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void writeToNetwork(ByteBuf buffer) {
|
||||||
|
buffer.writeByte(getPaletteHeader(palette.getVersion(), true));
|
||||||
|
|
||||||
|
for (int word : palette.getWords()) {
|
||||||
|
buffer.writeIntLE(word);
|
||||||
|
}
|
||||||
|
|
||||||
|
VarInts.writeUnsignedInt(buffer, ids.size());
|
||||||
|
ids.forEach(id -> {
|
||||||
|
VarInts.writeUnsignedInt(buffer, id);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void writeToStorage(ByteBuf buffer) {
|
||||||
|
buffer.writeByte(getPaletteHeader(palette.getVersion(), false));
|
||||||
|
for (int word : palette.getWords()) {
|
||||||
|
buffer.writeIntLE(word);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: Write persistent NBT tags
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void onResize(PaletteVersion version) {
|
||||||
|
Palette oldPalette = this.palette;
|
||||||
|
this.palette = version.createPalette(SIZE);
|
||||||
|
|
||||||
|
for (int i = 0; i < SIZE; i++) {
|
||||||
|
this.palette.set(i, oldPalette.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int idFor(int legacyId) {
|
||||||
|
int runtimeId = GlobalBlockPalette.getOrCreateRuntimeId(legacyId);
|
||||||
|
int index = this.ids.indexOf(runtimeId);
|
||||||
|
if (index != -1) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = this.ids.size();
|
||||||
|
this.ids.add(runtimeId);
|
||||||
|
PaletteVersion version = this.palette.getVersion();
|
||||||
|
if (index > version.getMaxEntryValue()) {
|
||||||
|
PaletteVersion next = version.next();
|
||||||
|
if (next != null) {
|
||||||
|
this.onResize(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getPaletteHeader(PaletteVersion version, boolean runtime) {
|
||||||
|
return (version.getVersion() << 1) | (runtime ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
for (int word : this.palette.getWords()) {
|
||||||
|
if (word != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockStorage copy() {
|
||||||
|
return new BlockStorage(this.palette.copy(), new TIntArrayList(this.ids));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package org.geysermc.connector.world.chunk;
|
||||||
|
|
||||||
|
import com.nukkitx.network.util.Preconditions;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import lombok.Synchronized;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
|
||||||
|
*/
|
||||||
|
public class ChunkSection {
|
||||||
|
|
||||||
|
private static final int CHUNK_SECTION_VERSION = 8;
|
||||||
|
public static final int SIZE = 4096;
|
||||||
|
|
||||||
|
private final BlockStorage[] storage;
|
||||||
|
private final NibbleArray blockLight;
|
||||||
|
private final NibbleArray skyLight;
|
||||||
|
|
||||||
|
public ChunkSection() {
|
||||||
|
this(new BlockStorage[]{new BlockStorage(), new BlockStorage()}, new NibbleArray(SIZE),
|
||||||
|
new NibbleArray(SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkSection(BlockStorage[] blockStorage) {
|
||||||
|
this(blockStorage, new NibbleArray(SIZE), new NibbleArray(SIZE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkSection(BlockStorage[] storage, byte[] blockLight, byte[] skyLight) {
|
||||||
|
Preconditions.checkNotNull(storage, "storage");
|
||||||
|
Preconditions.checkArgument(storage.length > 1, "Block storage length must be at least 2");
|
||||||
|
for (BlockStorage blockStorage : storage) {
|
||||||
|
Preconditions.checkNotNull(blockStorage, "storage");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.storage = storage;
|
||||||
|
this.blockLight = new NibbleArray(blockLight);
|
||||||
|
this.skyLight = new NibbleArray(skyLight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChunkSection(BlockStorage[] storage, NibbleArray blockLight, NibbleArray skyLight) {
|
||||||
|
this.storage = storage;
|
||||||
|
this.blockLight = blockLight;
|
||||||
|
this.skyLight = skyLight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFullBlock(int x, int y, int z, int layer) {
|
||||||
|
checkBounds(x, y, z);
|
||||||
|
Preconditions.checkElementIndex(layer, this.storage.length);
|
||||||
|
return this.storage[layer].getFullBlock(blockPosition(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFullBlock(int x, int y, int z, int layer, int fullBlock) {
|
||||||
|
checkBounds(x, y, z);
|
||||||
|
Preconditions.checkElementIndex(layer, this.storage.length);
|
||||||
|
this.storage[layer].setFullBlock(blockPosition(x, y, z), fullBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized("skyLight")
|
||||||
|
public byte getSkyLight(int x, int y, int z) {
|
||||||
|
checkBounds(x, y, z);
|
||||||
|
return this.skyLight.get(blockPosition(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized("skyLight")
|
||||||
|
public void setSkyLight(int x, int y, int z, byte val) {
|
||||||
|
checkBounds(x, y, z);
|
||||||
|
this.skyLight.set(blockPosition(x, y, z), val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized("blockLight")
|
||||||
|
public byte getBlockLight(int x, int y, int z) {
|
||||||
|
checkBounds(x, y, z);
|
||||||
|
return this.blockLight.get(blockPosition(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized("blockLight")
|
||||||
|
public void setBlockLight(int x, int y, int z, byte val) {
|
||||||
|
checkBounds(x, y, z);
|
||||||
|
this.blockLight.set(blockPosition(x, y, z), val);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeToNetwork(ByteBuf buffer) {
|
||||||
|
buffer.writeByte(CHUNK_SECTION_VERSION);
|
||||||
|
buffer.writeByte(this.storage.length);
|
||||||
|
for (BlockStorage blockStorage : this.storage) {
|
||||||
|
blockStorage.writeToNetwork(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public NibbleArray getSkyLightArray() {
|
||||||
|
return skyLight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NibbleArray getBlockLightArray() {
|
||||||
|
return blockLight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockStorage[] getBlockStorageArray() {
|
||||||
|
return storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
for (BlockStorage blockStorage : this.storage) {
|
||||||
|
if (!blockStorage.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkSection copy() {
|
||||||
|
BlockStorage[] storage = new BlockStorage[this.storage.length];
|
||||||
|
for (int i = 0; i < storage.length; i++) {
|
||||||
|
storage[i] = this.storage[i].copy();
|
||||||
|
}
|
||||||
|
return new ChunkSection(storage, skyLight.copy(), blockLight.copy());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int blockPosition(int x, int y, int z) {
|
||||||
|
return (x << 8) | (z << 4) | y;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkBounds(int x, int y, int z) {
|
||||||
|
Preconditions.checkArgument(x >= 0 && x < 16, "x (%s) is not between 0 and 15", x);
|
||||||
|
Preconditions.checkArgument(y >= 0 && y < 16, "y (%s) is not between 0 and 15", y);
|
||||||
|
Preconditions.checkArgument(z >= 0 && z < 16, "z (%s) is not between 0 and 15", z);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package org.geysermc.connector.world.chunk;
|
||||||
|
|
||||||
|
import com.nukkitx.network.util.Preconditions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
|
||||||
|
*/
|
||||||
|
public class NibbleArray implements Cloneable {
|
||||||
|
|
||||||
|
private final byte[] data;
|
||||||
|
|
||||||
|
public NibbleArray(int length) {
|
||||||
|
data = new byte[length / 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
public NibbleArray(byte[] array) {
|
||||||
|
data = array;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte get(int index) {
|
||||||
|
Preconditions.checkElementIndex(index, data.length * 2);
|
||||||
|
byte val = data[index / 2];
|
||||||
|
if ((index & 1) == 0) {
|
||||||
|
return (byte) (val & 0x0f);
|
||||||
|
} else {
|
||||||
|
return (byte) ((val & 0xf0) >>> 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(int index, byte value) {
|
||||||
|
Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15.");
|
||||||
|
Preconditions.checkElementIndex(index, data.length * 2);
|
||||||
|
value &= 0xf;
|
||||||
|
int half = index / 2;
|
||||||
|
byte previous = data[half];
|
||||||
|
if ((index & 1) == 0) {
|
||||||
|
data[half] = (byte) (previous & 0xf0 | value);
|
||||||
|
} else {
|
||||||
|
data[half] = (byte) (previous & 0x0f | value << 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fill(byte value) {
|
||||||
|
Preconditions.checkArgument(value >= 0 && value < 16, "Nibbles must have a value between 0 and 15.");
|
||||||
|
value &= 0xf;
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
data[i] = (byte) ((value << 4) | value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyFrom(byte[] bytes) {
|
||||||
|
Preconditions.checkNotNull(bytes, "bytes");
|
||||||
|
Preconditions.checkArgument(bytes.length == data.length, "length of provided byte array is %s but expected %s", bytes.length,
|
||||||
|
data.length);
|
||||||
|
System.arraycopy(bytes, 0, data, 0, data.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyFrom(NibbleArray array) {
|
||||||
|
Preconditions.checkNotNull(array, "array");
|
||||||
|
copyFrom(array.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NibbleArray copy() {
|
||||||
|
return new NibbleArray(getData().clone());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package org.geysermc.connector.world.chunk.palette;
|
||||||
|
|
||||||
|
import com.nukkitx.network.util.Preconditions;
|
||||||
|
import org.geysermc.connector.utils.MathUtils;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
|
||||||
|
*/
|
||||||
|
public class PaddedPalette implements Palette {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array used to store data
|
||||||
|
*/
|
||||||
|
private final int[] words;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Palette version information
|
||||||
|
*/
|
||||||
|
private final PaletteVersion version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of entries in this palette (<b>not</b> the length of the words array that internally backs this palette)
|
||||||
|
*/
|
||||||
|
private final int size;
|
||||||
|
|
||||||
|
PaddedPalette(PaletteVersion version, int size, int[] words) {
|
||||||
|
this.size = size;
|
||||||
|
this.version = version;
|
||||||
|
this.words = words;
|
||||||
|
int expectedWordsLength = MathUtils.ceil((float) size / version.entriesPerWord);
|
||||||
|
if (words.length != expectedWordsLength) {
|
||||||
|
throw new IllegalArgumentException("Invalid length given for storage, got: " + words.length +
|
||||||
|
" but expected: " + expectedWordsLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(int index, int value) {
|
||||||
|
Preconditions.checkElementIndex(index, this.size);
|
||||||
|
Preconditions.checkArgument(value >= 0 && value <= this.version.maxEntryValue, "Invalid value");
|
||||||
|
int arrayIndex = index / this.version.entriesPerWord;
|
||||||
|
int offset = (index % this.version.entriesPerWord) * this.version.bits;
|
||||||
|
|
||||||
|
this.words[arrayIndex] = this.words[arrayIndex] & ~(this.version.maxEntryValue << offset) | (value & this.version.maxEntryValue) << offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int get(int index) {
|
||||||
|
Preconditions.checkElementIndex(index, this.size);
|
||||||
|
int arrayIndex = index / this.version.entriesPerWord;
|
||||||
|
int offset = (index % this.version.entriesPerWord) * this.version.bits;
|
||||||
|
|
||||||
|
return (this.words[arrayIndex] >>> offset) & this.version.maxEntryValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return this.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getWords() {
|
||||||
|
return this.words;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaletteVersion getVersion() {
|
||||||
|
return this.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Palette copy() {
|
||||||
|
return new PaddedPalette(this.version, this.size, Arrays.copyOf(this.words, this.words.length));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.geysermc.connector.world.chunk.palette;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
|
||||||
|
*/
|
||||||
|
public interface Palette {
|
||||||
|
|
||||||
|
void set(int index, int value);
|
||||||
|
|
||||||
|
int get(int index);
|
||||||
|
|
||||||
|
int size();
|
||||||
|
|
||||||
|
int[] getWords();
|
||||||
|
|
||||||
|
PaletteVersion getVersion();
|
||||||
|
|
||||||
|
Palette copy();
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package org.geysermc.connector.world.chunk.palette;
|
||||||
|
|
||||||
|
import org.geysermc.connector.utils.MathUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
|
||||||
|
*/
|
||||||
|
public enum PaletteVersion {
|
||||||
|
|
||||||
|
V16(16, 2, null),
|
||||||
|
V8(8, 4, V16),
|
||||||
|
V6(6, 5, V8), // 2 bit padding
|
||||||
|
V5(5, 6, V6), // 2 bit padding
|
||||||
|
V4(4, 8, V5),
|
||||||
|
V3(3, 10, V4), // 2 bit padding
|
||||||
|
V2(2, 16, V3),
|
||||||
|
V1(1, 32, V2);
|
||||||
|
|
||||||
|
final byte bits;
|
||||||
|
final byte entriesPerWord;
|
||||||
|
final int maxEntryValue;
|
||||||
|
final PaletteVersion next;
|
||||||
|
|
||||||
|
PaletteVersion(int bits, int entriesPerWord, PaletteVersion next) {
|
||||||
|
this.bits = (byte) bits;
|
||||||
|
this.entriesPerWord = (byte) entriesPerWord;
|
||||||
|
this.maxEntryValue = (1 << this.bits) - 1;
|
||||||
|
this.next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Palette createPalette(int size) {
|
||||||
|
return this.createPalette(size, new int[MathUtils.ceil((float) size / entriesPerWord)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getVersion() {
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxEntryValue() {
|
||||||
|
return maxEntryValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaletteVersion next() {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Palette createPalette(int size, int[] words) {
|
||||||
|
if (this == V3 || this == V5 || this == V6) {
|
||||||
|
// Padded palettes aren't able to use bitwise operations due to their padding.
|
||||||
|
return new PaddedPalette(this, size, words);
|
||||||
|
} else {
|
||||||
|
return new Pow2Palette(this, size, words);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PaletteVersion getVersion(int version, boolean read) {
|
||||||
|
for (PaletteVersion ver : values()) {
|
||||||
|
if ( ( !read && ver.entriesPerWord <= version ) || ( read && ver.bits == version ) ) {
|
||||||
|
return ver;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Invalid palette version: " + version);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package org.geysermc.connector.world.chunk.palette;
|
||||||
|
|
||||||
|
import com.nukkitx.network.util.Preconditions;
|
||||||
|
import org.geysermc.connector.utils.MathUtils;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapted from NukkitX: https://github.com/NukkitX/Nukkit
|
||||||
|
*/
|
||||||
|
public class Pow2Palette implements Palette {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array used to store data
|
||||||
|
*/
|
||||||
|
private final int[] words;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Palette version information
|
||||||
|
*/
|
||||||
|
private final PaletteVersion version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of entries in this palette (<b>not</b> the length of the words array that internally backs this palette)
|
||||||
|
*/
|
||||||
|
private final int size;
|
||||||
|
|
||||||
|
Pow2Palette(PaletteVersion version, int size, int[] words) {
|
||||||
|
this.size = size;
|
||||||
|
this.version = version;
|
||||||
|
this.words = words;
|
||||||
|
int expectedWordsLength = MathUtils.ceil((float) size / version.entriesPerWord);
|
||||||
|
if (words.length != expectedWordsLength) {
|
||||||
|
throw new IllegalArgumentException("Invalid length given for storage, got: " + words.length +
|
||||||
|
" but expected: " + expectedWordsLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the entry at the given location to the given value
|
||||||
|
*/
|
||||||
|
public void set(int index, int value) {
|
||||||
|
Preconditions.checkElementIndex(index, this.size);
|
||||||
|
Preconditions.checkArgument(value >= 0 && value <= this.version.maxEntryValue, "Invalid value");
|
||||||
|
int bitIndex = index * this.version.bits;
|
||||||
|
int arrayIndex = bitIndex >> 5;
|
||||||
|
int offset = bitIndex & 31;
|
||||||
|
this.words[arrayIndex] = this.words[arrayIndex] & ~(this.version.maxEntryValue << offset) | (value & this.version.maxEntryValue) << offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the entry at the given index
|
||||||
|
*/
|
||||||
|
public int get(int index) {
|
||||||
|
Preconditions.checkElementIndex(index, this.size);
|
||||||
|
int bitIndex = index * this.version.bits;
|
||||||
|
int arrayIndex = bitIndex >> 5;
|
||||||
|
int wordOffset = bitIndex & 31;
|
||||||
|
return this.words[arrayIndex] >>> wordOffset & this.version.maxEntryValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the long array that is used to store the data in this BitArray. This is useful for sending packet data.
|
||||||
|
*/
|
||||||
|
public int size() {
|
||||||
|
return this.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* @return {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int[] getWords() {
|
||||||
|
return this.words;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PaletteVersion getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Palette copy() {
|
||||||
|
return new Pow2Palette(this.version, this.size, Arrays.copyOf(this.words, this.words.length));
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue