All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.nukkit.level.format.anvil.ChunkSection Maven / Gradle / Ivy

There is a newer version: 1.20.40-r1
Show newest version
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.Nonnull;
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;
    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().translateString("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);
                    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();
            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 {
            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();
            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
    @Nonnull
    @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));
    }

    @Nonnull
    @Override
    public Block getAndSetBlock(int x, int y, int z, Block block) {
        return getAndSetBlock(x, y, z, 0, block);
    }

    @PowerNukkitOnly
    @Nonnull
    @Override
    public Block getAndSetBlock(int x, int y, int z, int layer, Block block) {
        sectionLock.writeLock().lock();
        try {
            layerStorage.setNeedReObfuscate();
            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();
            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(@Nonnull 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
    @Nonnull
    @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().translateString("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)
    @Nonnull
    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();
    }

    @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 +
                '}';
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy