mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-08-14 23:57:35 +00:00
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>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.nukkitx.protocol</groupId>
|
||||
<artifactId>bedrock-v354</artifactId>
|
||||
<version>2.1.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.nukkitx.protocol</groupId>
|
||||
<artifactId>bedrock-v340</artifactId>
|
||||
<version>2.1.2</version>
|
||||
<groupId>com.nukkitx</groupId>
|
||||
<artifactId>fastutil-lite</artifactId>
|
||||
<version>8.1.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -43,7 +43,6 @@ import org.geysermc.connector.console.GeyserLogger;
|
|||
import org.geysermc.connector.metrics.Metrics;
|
||||
import org.geysermc.connector.network.ConnectorServerEventHandler;
|
||||
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.plugin.GeyserPluginLoader;
|
||||
import org.geysermc.connector.plugin.GeyserPluginManager;
|
||||
|
|
|
@ -27,8 +27,10 @@ package org.geysermc.connector.entity.attribute;
|
|||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
public class Attribute {
|
||||
|
||||
|
|
|
@ -98,6 +98,9 @@ public class ItemTranslator {
|
|||
|
||||
public static BedrockItem getBedrockBlock(BlockState stack) {
|
||||
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"));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +1,55 @@
|
|||
package org.geysermc.connector.network.translators.java.world;
|
||||
|
||||
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 io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.geysermc.connector.console.GeyserLogger;
|
||||
import org.geysermc.connector.network.session.GeyserSession;
|
||||
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> {
|
||||
|
||||
@Override
|
||||
public void translate(ServerChunkDataPacket packet, GeyserSession session) {
|
||||
LevelChunkPacket levelChunkPacket = new LevelChunkPacket();
|
||||
|
||||
Chunks.ChunkData data = Chunks.getData(packet.getColumn());
|
||||
levelChunkPacket.setSubChunksLength(data.count);
|
||||
levelChunkPacket.setData(data.bytes);
|
||||
levelChunkPacket.setChunkX(packet.getColumn().getX());
|
||||
levelChunkPacket.setChunkZ(packet.getColumn().getZ());
|
||||
try {
|
||||
byte[] buffer = new byte[32];
|
||||
ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn());
|
||||
|
||||
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!");
|
||||
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 org.geysermc.connector.network.translators.item.BedrockItem;
|
||||
import org.geysermc.connector.network.translators.item.JavaItem;
|
||||
import org.geysermc.connector.world.GlobalBlockPalette;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
|
@ -38,6 +39,7 @@ public class Toolbox {
|
|||
ByteBuf b = Unpooled.buffer();
|
||||
VarInts.writeUnsignedInt(b, entries.size());
|
||||
for (Map<String, Object> e : entries) {
|
||||
GlobalBlockPalette.registerMapping((int) e.get("id"));
|
||||
BedrockUtils.writeString(b, (String) e.get("name"));
|
||||
b.writeShortLE((int) e.get("data"));
|
||||
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