Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
cn.nukkit.level.format.anvil.ChunkSection Maven / Gradle / Ivy
package cn.nukkit.level.format.anvil;
import cn.nukkit.Server;
import cn.nukkit.api.DeprecationDetails;
import cn.nukkit.api.PowerNukkitOnly;
import cn.nukkit.api.PowerNukkitXOnly;
import cn.nukkit.api.Since;
import cn.nukkit.block.Block;
import cn.nukkit.block.BlockUnknown;
import cn.nukkit.blockstate.BlockState;
import cn.nukkit.blockstate.exception.InvalidBlockStateException;
import cn.nukkit.level.Level;
import cn.nukkit.level.format.ChunkSection3DBiome;
import cn.nukkit.level.format.LevelProvider;
import cn.nukkit.level.format.anvil.util.BlockStorage;
import cn.nukkit.level.format.anvil.util.ImmutableBlockStorage;
import cn.nukkit.level.format.anvil.util.NibbleArray;
import cn.nukkit.level.format.generic.EmptyChunkSection;
import cn.nukkit.level.format.updater.ChunkUpdater;
import cn.nukkit.math.BlockVector3;
import cn.nukkit.nbt.tag.ByteArrayTag;
import cn.nukkit.nbt.tag.CompoundTag;
import cn.nukkit.nbt.tag.IntTag;
import cn.nukkit.nbt.tag.ListTag;
import cn.nukkit.utils.*;
import it.unimi.dsi.fastutil.ints.*;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiPredicate;
/**
* @author MagicDroidX (Nukkit Project)
*/
@Log4j2
@SuppressWarnings("java:S2176")
@ParametersAreNonnullByDefault
public class ChunkSection implements cn.nukkit.level.format.ChunkSection, ChunkSection3DBiome {
@PowerNukkitOnly
public static final int STREAM_STORAGE_VERSION = 8;
@PowerNukkitOnly
public static final int SAVE_STORAGE_VERSION = 7;
private static final String STORAGE_TAG_NAME = "Storage";
private static final String HUGE_TAG_NAME = "DataHyper";
private static final String BIOME_TAG_NAME = "Biomes";
private static final BigInteger BYTE_MASK = BigInteger.valueOf(0xFF);
protected final ReentrantReadWriteLock sectionLock = new ReentrantReadWriteLock();
private final int y;
protected byte[] biomeId; // YZX
protected byte[] blockLight;
protected byte[] skyLight;
protected byte[] compressedLight;
protected boolean hasBlockLight;
protected boolean hasSkyLight;
@PowerNukkitXOnly
@Since("1.19.21-r1")
protected boolean invalidCustomBlockWhenLoad = false;
@PowerNukkitXOnly
@Since("1.19.60-r1")
protected long blockChanges = 0;
private LayerStorage layerStorage;
private int contentVersion;
private ChunkSection(
int y, LayerStorage layerStorage, @Nullable byte[] biomeId, @Nullable byte[] blockLight, @Nullable byte[] skyLight,
@Nullable byte[] compressedLight, boolean hasBlockLight, boolean hasSkyLight) {
this.y = y;
this.layerStorage = layerStorage;
this.biomeId = biomeId;
this.skyLight = skyLight;
this.blockLight = blockLight;
this.compressedLight = compressedLight;
this.hasBlockLight = hasBlockLight;
this.hasSkyLight = hasSkyLight;
}
public ChunkSection(int y) {
this.y = y;
this.contentVersion = ChunkUpdater.getCurrentContentVersion();
layerStorage = LayerStorage.EMPTY;
biomeId = new byte[4096];
hasBlockLight = false;
hasSkyLight = false;
}
public ChunkSection(CompoundTag nbt) {
this(nbt, null);
}
public ChunkSection(CompoundTag nbt, @Nullable byte[] default2DBiomeId) {
this.y = nbt.getByte("Y");
setContentVersion(nbt.getByte("ContentVersion"));
int version = nbt.getByte("Version");
ListTag storageTagList = getStorageTagList(nbt, version);
switch (storageTagList.size()) {
case 0:
layerStorage = LayerStorage.EMPTY;
break;
case 1:
layerStorage = new SingleLayerStorage();
break;
default:
layerStorage = new MultiLayerStorage(ImmutableBlockStorage.EMPTY, ImmutableBlockStorage.EMPTY);
break;
}
var customBlocksIdNbt = nbt.getCompound("CustomBlocksIdMap");
if (customBlocksIdNbt == null) {
customBlocksIdNbt = new CompoundTag();
}
var customBlocksIdMap = new Int2ObjectOpenHashMap(customBlocksIdNbt.getTags().size() + 1, 0.9999f);
for (var entry : customBlocksIdNbt.getTags().entrySet()) {
customBlocksIdMap.put(((IntTag) entry.getValue()).getData().intValue(), entry.getKey());
}
for (int i = 0; i < storageTagList.size(); i++) {
CompoundTag storageTag = storageTagList.get(i);
loadStorage(i, storageTag, customBlocksIdMap);
}
layerStorage.compress(this::setLayerStorage);
this.blockLight = nbt.getByteArray("BlockLight");
this.skyLight = nbt.getByteArray("SkyLight");
if (nbt.contains(BIOME_TAG_NAME)) {
this.biomeId = nbt.getByteArray(BIOME_TAG_NAME);
} else if (default2DBiomeId != null && default2DBiomeId.length >= 256) {
this.biomeId = new byte[4096];
for (int dx = 0; dx < 16; dx++) {
for (int dz = 0; dz < 16; dz++) {
for (int dy = 0; dy < 16; dy++) {
this.biomeId[getAnvilIndex(dx, dy, dz)] = default2DBiomeId[(dx << 4) | dz];
}
}
}
} else {
this.biomeId = new byte[4096];
}
}
private static BlockState loadState(int index, int blockId, int composedData, ListTag hugeDataList, int hugeDataSize) {
if (hugeDataSize == 0) {
return BlockState.of(blockId, composedData);
} else if (hugeDataSize < 3) {
return loadHugeIntData(index, blockId, composedData, hugeDataList, hugeDataSize);
} else if (hugeDataSize < 7) {
return loadHugeLongData(index, blockId, composedData, hugeDataList, hugeDataSize);
} else {
return loadHugeBigData(index, blockId, composedData, hugeDataList, hugeDataSize);
}
}
private static BlockState loadHugeIntData(int index, int blockId, int composedData, ListTag hugeDataList, int hugeDataSize) {
int data = composedData;
for (int dataIndex = 0; dataIndex < hugeDataSize; dataIndex++) {
int longPart = (hugeDataList.get(dataIndex).data[index] & 0xFF) << 8 << (8 * dataIndex);
data |= longPart;
}
return BlockState.of(blockId, data);
}
private static BlockState loadHugeLongData(int index, int blockId, int composedData, ListTag hugeDataList, int hugeDataSize) {
long data = composedData;
for (int dataIndex = 0; dataIndex < hugeDataSize; dataIndex++) {
long longPart = (hugeDataList.get(dataIndex).data[index] & 0xFFL) << 8 << (8 * dataIndex);
data |= longPart;
}
return BlockState.of(blockId, data);
}
private static BlockState loadHugeBigData(int index, int blockId, int composedData, ListTag hugeDataList, int hugeDataSize) {
BigInteger data = BigInteger.valueOf(composedData);
for (int dataIndex = 0; dataIndex < hugeDataSize; dataIndex++) {
BigInteger hugePart = BigInteger.valueOf((hugeDataList.get(dataIndex).data[index] & 0xFFL) << 8).shiftLeft(8 * dataIndex);
data = data.or(hugePart);
}
return BlockState.of(blockId, data);
}
private static ListTag getStorageTagList(CompoundTag nbt, int version) {
ListTag storageTagList;
if (version == SAVE_STORAGE_VERSION || version == 8) {
storageTagList = nbt.getList(STORAGE_TAG_NAME, CompoundTag.class);
} else if (version == 0 || version == 1) {
storageTagList = new ListTag<>(STORAGE_TAG_NAME);
storageTagList.add(nbt);
} else {
throw new ChunkException("Unsupported chunk section version: " + version);
}
return storageTagList;
}
private static int composeBlockId(byte baseId, byte extraId) {
return ((extraId & 0xFF) << 8) | (baseId & 0xFF);
}
private static int composeData(byte baseData, byte extraData) {
return ((extraData & 0xF) << 4) | (baseData & 0xF);
}
private static int getAnvilIndex(int x, int y, int z) {
return (y << 8) + (z << 4) + x; // YZX
}
private void loadStorage(int layer, CompoundTag storageTag, @NotNull Int2ObjectMap customBlocksIdMap) {
byte[] blocks = storageTag.getByteArray("Blocks");
boolean hasBlockIds = false;
if (blocks.length == 0) {
blocks = EmptyChunkSection.EMPTY_ID_ARRAY;
} else {
hasBlockIds = true;
}
byte[] blocksExtra = storageTag.getByteArray("BlocksExtra");
if (blocksExtra.length == 0) {
blocksExtra = EmptyChunkSection.EMPTY_ID_ARRAY;
}
byte[] dataBytes = storageTag.getByteArray("Data");
NibbleArray data;
if (dataBytes.length == 0) {
data = NibbleArray.EMPTY_DATA_ARRAY;
} else {
hasBlockIds = true;
data = new NibbleArray(dataBytes);
}
byte[] dataExtraBytes = storageTag.getByteArray("DataExtra");
if (dataExtraBytes.length == 0) {
dataExtraBytes = EmptyChunkSection.EMPTY_DATA_ARRAY;
}
NibbleArray dataExtra = new NibbleArray(dataExtraBytes);
ListTag hugeDataList = storageTag.getList(HUGE_TAG_NAME, ByteArrayTag.class);
int hugeDataSize = hugeDataList.size();
if (!hasBlockIds && hugeDataSize == 0) {
return;
}
if (getContentVersion() > ChunkUpdater.getCurrentContentVersion()) {
log.warn(
"Loading a chunk section with content version ({}) higher than the current version ({}), " +
"Errors may occur and the chunk may get corrupted blocks!",
getContentVersion(), ChunkUpdater.getCurrentContentVersion()
);
}
BlockStorage storage = layerStorage.getOrSetStorage(this::setLayerStorage, this::getContentVersion, layer);
var currentCustomBlocksIdMap = new Int2IntOpenHashMap(customBlocksIdMap.size() + 1, 0.9999f);
// Convert YZX to XZY
for (int bx = 0; bx < 16; bx++) {
for (int bz = 0; bz < 16; bz++) {
for (int by = 0; by < 16; by++) {
int index = getAnvilIndex(bx, by, bz);
int blockId = composeBlockId(blocks[index], blocksExtra[index]);
if (blockId > Block.MAX_BLOCK_ID) {
if (currentCustomBlocksIdMap.containsKey(blockId)) {
blockId = currentCustomBlocksIdMap.get(blockId);
} else {
var namespaceId = customBlocksIdMap.get(blockId);
if (namespaceId != null) {
var tmp = Block.CUSTOM_BLOCK_ID_MAP.get(namespaceId);
if (tmp == null) {
log.warn(Server.getInstance().getLanguage().tr("nukkit.anvil.load.unknown-custom-block", namespaceId));
storage.setBlockState(bx, by, bz, BlockState.AIR);
invalidCustomBlockWhenLoad = true;
continue;
}
currentCustomBlocksIdMap.put(blockId, blockId = tmp);
}
}
}
int composedData = composeData(data.get(index), dataExtra.get(index));
BlockState state = loadState(index, blockId, composedData, hugeDataList, hugeDataSize);
//TODO 回退之前的方块更新方案,现在有更好的解决方式,下个版本移除这段代码
switch (state.getBlockId()) {
case 807 -> storage.setBlockState(bx, by, bz, Block.get(35, 8).getCurrentState());
case 808 -> storage.setBlockState(bx, by, bz, Block.get(35, 7).getCurrentState());
case 809 -> storage.setBlockState(bx, by, bz, Block.get(35, 15).getCurrentState());
case 810 -> storage.setBlockState(bx, by, bz, Block.get(35, 12).getCurrentState());
case 811 -> storage.setBlockState(bx, by, bz, Block.get(35, 14).getCurrentState());
case 812 -> storage.setBlockState(bx, by, bz, Block.get(35, 1).getCurrentState());
case 813 -> storage.setBlockState(bx, by, bz, Block.get(35, 4).getCurrentState());
case 814 -> storage.setBlockState(bx, by, bz, Block.get(35, 5).getCurrentState());
case 815 -> storage.setBlockState(bx, by, bz, Block.get(35, 13).getCurrentState());
case 816 -> storage.setBlockState(bx, by, bz, Block.get(35, 9).getCurrentState());
case 817 -> storage.setBlockState(bx, by, bz, Block.get(35, 3).getCurrentState());
case 818 -> storage.setBlockState(bx, by, bz, Block.get(35, 11).getCurrentState());
case 819 -> storage.setBlockState(bx, by, bz, Block.get(35, 10).getCurrentState());
case 820 -> storage.setBlockState(bx, by, bz, Block.get(35, 2).getCurrentState());
case 821 -> storage.setBlockState(bx, by, bz, Block.get(35, 6).getCurrentState());
default -> storage.setBlockState(bx, by, bz, state);
}
}
}
}
}
@Override
public int getY() {
return y;
}
@Override
public int getBlockId(int x, int y, int z) {
return getBlockId(x, y, z, 0);
}
@PowerNukkitOnly
@Override
public int getBlockId(int x, int y, int z, int layer) {
sectionLock.readLock().lock();
try {
return layerStorage.getStorageOrEmpty(layer).getBlockId(x, y, z);
} finally {
sectionLock.readLock().unlock();
}
}
@Override
public void setBlockId(int x, int y, int z, int id) {
setBlockId(x, y, z, 0, id);
}
@PowerNukkitOnly
@Override
public void setBlockId(int x, int y, int z, int layer, int id) {
sectionLock.writeLock().lock();
try {
layerStorage.setNeedReObfuscate();
addBlockChange();
if (id != 0) {
layerStorage.getOrSetStorage(this::setLayerStorage, this::getContentVersion, layer).setBlockId(x, y, z, id);
} else {
BlockStorage storage = layerStorage.getStorageOrNull(layer);
if (storage != null) {
storage.setBlockId(x, y, z, id);
}
}
} finally {
sectionLock.writeLock().unlock();
}
}
private void setLayerStorage(LayerStorage storage) {
this.layerStorage = storage;
}
@Deprecated
@DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.4.0.0-PN")
@Override
public boolean setFullBlockId(int x, int y, int z, int fullId) {
setFullBlockId(x, y, z, 0, fullId);
return true;
}
@Deprecated
@DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.4.0.0-PN")
@PowerNukkitOnly
@Override
public boolean setFullBlockId(int x, int y, int z, int layer, int fullId) {
sectionLock.writeLock().lock();
try {
addBlockChange();
if (fullId != 0) {
layerStorage.getOrSetStorage(this::setLayerStorage, this::getContentVersion, layer).setFullBlock(x, y, z, fullId);
} else {
BlockStorage storage = layerStorage.getStorageOrNull(layer);
if (storage != null) {
storage.setFullBlock(x, y, z, fullId);
}
}
return true;
} finally {
sectionLock.writeLock().unlock();
}
}
@Deprecated
@DeprecationDetails(reason = "The data is limited to 32 bits", replaceWith = "getBlockState", since = "1.4.0.0-PN")
@Override
public int getBlockData(int x, int y, int z) {
return getBlockData(x, y, z, 0);
}
@Deprecated
@DeprecationDetails(reason = "The data is limited to 32 bits", replaceWith = "getBlockState", since = "1.4.0.0-PN")
@PowerNukkitOnly
@Override
public int getBlockData(int x, int y, int z, int layer) {
sectionLock.readLock().lock();
try {
return layerStorage.getStorageOrEmpty(layer).getBlockData(x, y, z);
} finally {
sectionLock.readLock().unlock();
}
}
@Deprecated
@DeprecationDetails(reason = "The data is limited to 32 bits", replaceWith = "getBlockState", since = "1.4.0.0-PN")
@Override
public void setBlockData(int x, int y, int z, int data) {
setBlockData(x, y, z, 0, data);
}
@Deprecated
@DeprecationDetails(reason = "The data is limited to 32 bits", replaceWith = "getBlockState", since = "1.4.0.0-PN")
@PowerNukkitOnly
@Override
public void setBlockData(int x, int y, int z, int layer, int data) {
sectionLock.writeLock().lock();
try {
layerStorage.setNeedReObfuscate();
addBlockChange();
if (data != 0) {
layerStorage.getOrSetStorage(this::setLayerStorage, this::getContentVersion, layer).setBlockData(x, y, z, data);
} else {
BlockStorage storage = layerStorage.getStorageOrNull(layer);
if (storage != null) {
storage.setBlockData(x, y, z, data);
}
}
} finally {
sectionLock.writeLock().unlock();
}
}
@Deprecated
@DeprecationDetails(reason = "The data is limited to 32 bits", replaceWith = "getBlockState", since = "1.4.0.0-PN")
@Override
public int getFullBlock(int x, int y, int z) {
return getFullBlock(x, y, z, 0);
}
@PowerNukkitOnly
@NotNull
@Override
public BlockState getBlockState(int x, int y, int z, int layer) {
sectionLock.readLock().lock();
try {
return layerStorage.getStorageOrEmpty(layer).getBlockState(x, y, z);
} finally {
sectionLock.readLock().unlock();
}
}
@PowerNukkitOnly
@Deprecated
@DeprecationDetails(reason = "The data is limited to 32 bits", replaceWith = "getBlockState", since = "1.4.0.0-PN")
@Override
public int getFullBlock(int x, int y, int z, int layer) {
sectionLock.readLock().lock();
try {
return layerStorage.getStorageOrEmpty(layer).getFullBlock(x, y, z);
} finally {
sectionLock.readLock().unlock();
}
}
@Override
public boolean setBlock(int x, int y, int z, int blockId) {
return setBlockStateAtLayer(x, y, z, 0, BlockState.of(blockId));
}
@PowerNukkitOnly
@Override
public boolean setBlockAtLayer(int x, int y, int z, int layer, int blockId) {
return setBlockStateAtLayer(x, y, z, layer, BlockState.of(blockId));
}
@NotNull
@Override
public Block getAndSetBlock(int x, int y, int z, Block block) {
return getAndSetBlock(x, y, z, 0, block);
}
@PowerNukkitOnly
@NotNull
@Override
public Block getAndSetBlock(int x, int y, int z, int layer, Block block) {
sectionLock.writeLock().lock();
try {
layerStorage.setNeedReObfuscate();
addBlockChange();
BlockStorage storage;
if (block.getId() != 0 || !block.isDefaultState()) {
storage = layerStorage.getOrSetStorage(this::setLayerStorage, this::getContentVersion, layer);
} else {
storage = layerStorage.getStorageOrNull(layer);
if (storage == null) {
return BlockState.AIR.getBlock();
}
}
BlockState state = storage.getAndSetBlockState(x, y, z, block.getCurrentState());
try {
return state.getBlock();
} catch (InvalidBlockStateException ignored) {
return new BlockUnknown(state.getBlockId(), state.getExactIntStorage());
}
} finally {
sectionLock.writeLock().unlock();
}
}
@PowerNukkitOnly
@Since("1.4.0.0-PN")
@Override
public BlockState getAndSetBlockState(int x, int y, int z, int layer, BlockState state) {
sectionLock.writeLock().lock();
try {
layerStorage.setNeedReObfuscate();
addBlockChange();
if (!BlockState.AIR.equals(state)) {
return layerStorage.getOrSetStorage(this::setLayerStorage, this::getContentVersion, layer).getAndSetBlockState(x, y, z, state);
} else {
BlockStorage storage = layerStorage.getStorageOrNull(layer);
if (storage == null) {
return BlockState.AIR;
}
return storage.getAndSetBlockState(x, y, z, state);
}
} finally {
sectionLock.writeLock().unlock();
}
}
@Deprecated
@DeprecationDetails(reason = "The data is limited to 32 bits", replaceWith = "getBlockState", since = "1.4.0.0-PN")
@Override
public boolean setBlock(int x, int y, int z, int blockId, int meta) {
return setBlockAtLayer(x, y, z, 0, blockId, meta);
}
@Deprecated
@DeprecationDetails(reason = "The data is limited to 32 bits", replaceWith = "getBlockState", since = "1.4.0.0-PN")
@PowerNukkitOnly
@Override
public boolean setBlockAtLayer(int x, int y, int z, int layer, int blockId, int meta) {
return setBlockStateAtLayer(x, y, z, layer, BlockState.of(blockId, meta));
}
@PowerNukkitOnly
@Override
public boolean setBlockStateAtLayer(int x, int y, int z, int layer, BlockState state) {
BlockState previous = getAndSetBlockState(x, y, z, layer, state);
return !state.equals(previous);
}
@PowerNukkitOnly
@Override
public int getBlockChangeStateAbove(int x, int y, int z) {
sectionLock.readLock().lock();
try {
BlockStorage storage = layerStorage.getStorageOrNull(0);
if (storage == null) {
return 0;
}
return storage.getBlockChangeStateAbove(x, y, z);
} finally {
sectionLock.readLock().unlock();
}
}
@Since("1.4.0.0-PN")
@PowerNukkitOnly
@Override
public synchronized void delayPaletteUpdates() {
sectionLock.writeLock().lock();
try {
layerStorage.delayPaletteUpdates();
} finally {
sectionLock.writeLock().unlock();
}
}
@Override
public int getBlockSkyLight(int x, int y, int z) {
if (this.skyLight == null) {
if (!hasSkyLight) {
return 0;
} else if (compressedLight == null) {
return 15;
}
this.skyLight = getSkyLightArray();
}
int sl = this.skyLight[(y << 7) | (z << 3) | (x >> 1)] & 0xff;
if ((x & 1) == 0) {
return sl & 0x0f;
}
return sl >> 4;
}
@Override
public void setBlockSkyLight(int x, int y, int z, int level) {
if (this.skyLight == null) {
if (hasSkyLight && compressedLight != null) {
this.skyLight = getSkyLightArray();
} else if (level == (hasSkyLight ? 15 : 0)) {
return;
} else {
this.skyLight = new byte[2048];
if (hasSkyLight) {
Arrays.fill(this.skyLight, (byte) 0xFF);
}
}
}
int i = (y << 7) | (z << 3) | (x >> 1);
int old = this.skyLight[i] & 0xff;
if ((x & 1) == 0) {
this.skyLight[i] = (byte) ((old & 0xf0) | (level & 0x0f));
} else {
this.skyLight[i] = (byte) (((level & 0x0f) << 4) | (old & 0x0f));
}
}
@Override
public int getBlockLight(int x, int y, int z) {
if (blockLight == null && !hasBlockLight) return 0;
this.blockLight = getLightArray();
int l = blockLight[(y << 7) | (z << 3) | (x >> 1)] & 0xff;
if ((x & 1) == 0) {
return l & 0x0f;
}
return l >> 4;
}
@Override
public void setBlockLight(int x, int y, int z, int level) {
if (this.blockLight == null) {
if (hasBlockLight) {
this.blockLight = getLightArray();
} else if (level == 0) {
return;
} else {
this.blockLight = new byte[2048];
}
}
int i = (y << 7) | (z << 3) | (x >> 1);
int old = this.blockLight[i] & 0xff;
if ((x & 1) == 0) {
this.blockLight[i] = (byte) ((old & 0xf0) | (level & 0x0f));
} else {
this.blockLight[i] = (byte) (((level & 0x0f) << 4) | (old & 0x0f));
}
}
@Override
public byte[] getSkyLightArray() {
if (skyLight != null) {
return skyLight.clone();
}
if (!hasSkyLight) {
return new byte[EmptyChunkSection.EMPTY_LIGHT_ARR.length];
}
if (compressedLight != null && inflate() && skyLight != null) {
return skyLight.clone();
}
return EmptyChunkSection.EMPTY_SKY_LIGHT_ARR.clone();
}
private boolean inflate() {
try {
if (compressedLight != null && compressedLight.length != 0) {
byte[] inflated = Zlib.inflate(compressedLight);
blockLight = Arrays.copyOfRange(inflated, 0, 2048);
if (inflated.length > 2048) {
skyLight = Arrays.copyOfRange(inflated, 2048, 4096);
} else {
skyLight = new byte[2048];
if (hasSkyLight) {
Arrays.fill(skyLight, (byte) 0xFF);
}
}
compressedLight = null;
} else {
blockLight = new byte[2048];
skyLight = new byte[2048];
if (hasSkyLight) {
Arrays.fill(skyLight, (byte) 0xFF);
}
}
return true;
} catch (IOException e) {
log.error("Failed to decompress a chunk section", e);
return false;
}
}
@Override
public byte[] getLightArray() {
if (blockLight != null) {
return blockLight.clone();
}
if (hasBlockLight && compressedLight != null && inflate() && blockLight != null) {
return blockLight.clone();
}
return new byte[EmptyChunkSection.EMPTY_LIGHT_ARR.length];
}
@Override
public boolean isEmpty() {
return false;
}
private byte[] toXZY(char[] raw) {
byte[] buffer = ThreadCache.byteCache6144.get();
for (int i = 0; i < 4096; i++) {
buffer[i] = (byte) (raw[i] >> 4);
}
for (int i = 0, j = 4096; i < 4096; i += 2, j++) {
buffer[j] = (byte) (((raw[i + 1] & 0xF) << 4) | (raw[i] & 0xF));
}
return buffer;
}
@Override
public synchronized void writeTo(@NotNull BinaryStream stream) {
layerStorage.writeTo(stream);
}
@Since("1.19.21-r1")
@Override
public void writeObfuscatedTo(BinaryStream stream, Level level) {
layerStorage.writeObfuscatedTo(stream, level);
}
@SuppressWarnings("java:S1905")
@Nullable
private List saveData(
BlockStorage storage, byte[] idsBase, @Nullable byte[] idsExtra,
NibbleArray dataBase, @Nullable NibbleArray dataExtra, @NotNull IntSet usedCustomBlockIds) {
boolean huge = storage.hasBlockDataHuge();
boolean big = huge || storage.hasBlockDataBig();
List hugeList = big ? new ArrayList<>(huge ? 3 : 1) : null;
if (big) {
hugeList.add(new byte[BlockStorage.SECTION_SIZE]);
}
storage.iterateStates(((bx, by, bz, state) -> {
int anvil = getAnvilIndex(bx, by, bz);
int blockId = state.getBlockId();
if (blockId == 0) {
return;
}
if (blockId > Block.MAX_BLOCK_ID) {
usedCustomBlockIds.add(blockId);
}
idsBase[anvil] = (byte) (blockId & 0xFF);
if (idsExtra != null) {
idsExtra[anvil] = (byte) (blockId >>> 8 & 0xFF);
}
@SuppressWarnings("deprecation")
int unsignedIntData = state.getBigDamage();
dataBase.set(anvil, (byte) (unsignedIntData & 0x0F));
if (dataExtra != null) {
dataExtra.set(anvil, (byte) (unsignedIntData >>> 4 & 0x0F));
}
if (!big) {
return;
}
hugeList.get(0)[anvil] = (byte) (unsignedIntData >>> 8 & 0xFF);
if (huge) {
saveHugeData(hugeList, state, anvil, unsignedIntData);
}
}));
return hugeList;
}
private void saveHugeData(List hugeList, BlockState state, int anvil, int intData) {
int bitSize = state.getBitSize();
if (bitSize <= 16) {
return;
}
intData >>>= 16;
int processedBits = 16;
int pos = 1;
for (; processedBits < 32 && processedBits <= bitSize; processedBits += 8, pos++, intData >>>= 8) {
byte[] blob = allocateBlob(hugeList, pos);
blob[anvil] = (byte) (intData & 0xFF);
}
if (processedBits >= bitSize) {
return;
}
BigInteger hugeData = state.getHugeDamage().shiftRight(32);
for (; processedBits <= bitSize; processedBits += 8, pos++, hugeData = hugeData.shiftRight(8)) {
byte[] blob = allocateBlob(hugeList, pos);
blob[anvil] = hugeData.and(BYTE_MASK).byteValue();
}
}
private byte[] allocateBlob(List hugeList, int pos) {
byte[] blob;
if (hugeList.size() <= pos) {
blob = new byte[BlockStorage.SECTION_SIZE];
hugeList.add(blob);
} else {
blob = hugeList.get(pos);
}
return blob;
}
@PowerNukkitOnly
@NotNull
@Override
public synchronized CompoundTag toNBT() {
CompoundTag s = new CompoundTag();
compressStorageLayers();
// For simplicity, not using the actual palette format to save in the disk
// And for better compatibility, attempting to use the closest to the old format as possible
// Version 0 = old format (single block storage, Blocks and Data tags only)
// Version 1 = old format extended same as 0 but may have BlocksExtra and DataExtra
// Version 7 = new format (multiple block storage, may have Blocks, BlocksExtra, Data and DataExtra)
// Version 8 = not the same as network version 8 because it's not pallet, it's like 7 but everything is filled even when an entire section is empty
s.putByte("Y", (getY()));
int version = SAVE_STORAGE_VERSION;
ListTag storageList = new ListTag<>(STORAGE_TAG_NAME);
int blockStorages = Math.max(1, layerStorage.size());
for (int layer = 0; layer < blockStorages; layer++) {
BlockStorage storage = layerStorage.getStorageOrEmpty(layer);
CompoundTag storageTag;
if (layer == 0 && blockStorages == 1) {
storageTag = s;
if (!storage.hasBlockDataExtras() && !storage.hasBlockIdExtras()) {
version = 0;
} else {
version = 1;
}
} else {
storageTag = new CompoundTag();
}
if (version == 0 || storage.hasBlockIds()) {
byte[] idsBase = new byte[BlockStorage.SECTION_SIZE];
byte[] idsExtra = storage.hasBlockIdExtras() ? new byte[BlockStorage.SECTION_SIZE] : null;
NibbleArray dataBase = new NibbleArray(BlockStorage.SECTION_SIZE);
NibbleArray dataExtra = storage.hasBlockDataExtras() ? new NibbleArray(BlockStorage.SECTION_SIZE) : null;
IntSet usedCustomIds = new IntOpenHashSet();
List dataHuge = saveData(storage, idsBase, idsExtra, dataBase, dataExtra, usedCustomIds);
storageTag.putByteArray("Blocks", idsBase);
storageTag.putByteArray("Data", dataBase.getData());
if (idsExtra != null) {
storageTag.putByteArray("BlocksExtra", idsExtra);
}
if (dataExtra != null) {
storageTag.putByteArray("DataExtra", dataExtra.getData());
}
if (dataHuge != null) {
ListTag hugeDataListTag = new ListTag<>(HUGE_TAG_NAME);
for (byte[] hugeData : dataHuge) {
hugeDataListTag.add(new ByteArrayTag("", hugeData));
}
storageTag.putList(hugeDataListTag);
}
if (!usedCustomIds.isEmpty()) {
var customBlocksIdMap = new CompoundTag();
for (IntIterator iterator = usedCustomIds.intIterator(); iterator.hasNext(); ) {
int each = iterator.nextInt();
var namespaceId = Block.ID_TO_CUSTOM_BLOCK.get(each).getNamespaceId();
if (namespaceId == null) {
log.warn(Server.getInstance().getLanguage().tr("nukkit.anvil.save.unknown-custom-block", each));
} else {
customBlocksIdMap.putInt(namespaceId, each);
}
}
storageTag.putCompound("CustomBlocksIdMap", customBlocksIdMap);
}
}
if (version >= SAVE_STORAGE_VERSION) {
storageList.add(storageTag);
}
}
s.putByte("Version", version);
s.putByte("ContentVersion", getContentVersion());
if (version >= SAVE_STORAGE_VERSION) {
s.putList(storageList);
}
s.putByteArray("BlockLight", blockLight == null ? getLightArray() : blockLight);
s.putByteArray("SkyLight", skyLight == null ? getSkyLightArray() : skyLight);
s.putByteArray(BIOME_TAG_NAME, biomeId == null ? EmptyChunkSection.EMPTY_BIOME_ARRAY : biomeId);
return s;
}
@PowerNukkitOnly
@Since("1.4.0.0-PN")
@Override
public synchronized void compressStorageLayers() {
layerStorage.compress(this::setLayerStorage);
}
public boolean compress() {
if (blockLight != null) {
byte[] arr1 = blockLight;
hasBlockLight = !Utils.isByteArrayEmpty(arr1);
byte[] arr2;
if (skyLight != null) {
arr2 = skyLight;
hasSkyLight = !Utils.isByteArrayEmpty(arr2);
} else if (hasSkyLight) {
arr2 = EmptyChunkSection.EMPTY_SKY_LIGHT_ARR;
} else {
arr2 = EmptyChunkSection.EMPTY_LIGHT_ARR;
hasSkyLight = false;
}
blockLight = null;
skyLight = null;
byte[] toDeflate = null;
if (hasBlockLight && hasSkyLight && arr2 != EmptyChunkSection.EMPTY_SKY_LIGHT_ARR) {
toDeflate = Binary.appendBytes(arr1, arr2);
} else if (hasBlockLight) {
toDeflate = arr1;
}
if (toDeflate != null) {
try {
compressedLight = Zlib.deflate(toDeflate, 1);
} catch (Exception e) {
log.error("Error compressing the light data", e);
}
}
return true;
}
return false;
}
@Since("1.4.0.0-PN")
@PowerNukkitOnly
@Override
public List scanBlocks(LevelProvider provider, int offsetX, int offsetZ, BlockVector3 min, BlockVector3 max, BiPredicate condition) {
sectionLock.readLock().lock();
try {
BlockStorage storage = layerStorage.getStorageOrNull(0);
if (storage == null) {
return Collections.emptyList();
}
final List results = new ArrayList<>();
final BlockVector3 current = new BlockVector3();
final boolean isOverWorld = provider.isOverWorld();
int offsetY = getY() << 4;
int minX = Math.max(0, min.x - offsetX);
int minY = Math.max(0, min.y - offsetY);
int minZ = Math.max(0, min.z - offsetZ);
for (int x = Math.min(max.x - offsetX, 15); x >= minX; x--) {
current.x = offsetX + x;
for (int z = Math.min(max.z - offsetZ, 15); z >= minZ; z--) {
current.z = offsetZ + z;
for (int y = Math.min(max.y - offsetY, 15); y >= minY; y--) {
current.y = offsetY + y;
BlockState state = storage.getBlockState(x, y, z);
if (condition.test(current, state)) {
if (isOverWorld) {
current.y -= 64;
results.add(state.getBlockRepairing(provider.getLevel(), current, 0));
current.y += 64;
} else {
results.add(state.getBlockRepairing(provider.getLevel(), current, 0));
}
}
}
}
}
return results;
} finally {
sectionLock.readLock().unlock();
}
}
@Override
@SneakyThrows(CloneNotSupportedException.class)
@NotNull
public ChunkSection copy() {
return new ChunkSection(
this.y,
this.layerStorage.clone(),
this.biomeId == null ? null : this.biomeId.clone(),
this.blockLight == null ? null : this.blockLight.clone(),
this.skyLight == null ? null : this.skyLight.clone(),
this.compressedLight == null ? null : this.compressedLight.clone(),
this.hasBlockLight,
this.hasSkyLight
);
}
@PowerNukkitOnly
@Override
public int getMaximumLayer() {
return 1;
}
@PowerNukkitOnly("Needed for level backward compatibility")
@Since("1.3.0.0-PN")
@Override
public int getContentVersion() {
return contentVersion;
}
@PowerNukkitOnly("Needed for level backward compatibility")
@Since("1.3.1.0-PN")
@Override
public void setContentVersion(int contentVersion) {
this.contentVersion = contentVersion;
}
@PowerNukkitOnly
@Override
public boolean hasBlocks() {
return layerStorage.hasBlocks();
}
@Override
public int getBiomeId(int x, int y, int z) {
return this.biomeId[getAnvilIndex(x, y, z)];
}
@Override
public void setBiomeId(int x, int y, int z, byte id) {
this.biomeId[getAnvilIndex(x, y, z)] = id;
}
@Override
public byte[] get3DBiomeDataArray() {
return this.biomeId;
}
@Override
public void set3DBiomeDataArray(byte[] data) {
if (data.length != 4096) {
throw new ChunkException("Invalid biome data length, expected 4096, got " + data.length);
}
System.arraycopy(data, 0, this.biomeId, 0, 4096);
}
@PowerNukkitXOnly
@Since("1.19.21-r1")
@Override
public void setNeedReObfuscate() {
layerStorage.setNeedReObfuscate();
}
@Since("1.19.60-r1")
@Override
public long getBlockChanges() {
return this.blockChanges;
}
@Since("1.19.60-r1")
@Override
public void addBlockChange() {
this.blockChanges++;
}
@Override
public String toString() {
return "ChunkSection{" +
"y=" + y +
", sectionLock=" + sectionLock +
", layerStorage=" + layerStorage +
", biomeId=" + Arrays.toString(biomeId) +
", blockLight=" + Arrays.toString(blockLight) +
", skyLight=" + Arrays.toString(skyLight) +
", compressedLight=" + Arrays.toString(compressedLight) +
", hasBlockLight=" + hasBlockLight +
", hasSkyLight=" + hasSkyLight +
", contentVersion=" + contentVersion +
'}';
}
}