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

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

There is a newer version: 1.20.40-r1
Show newest version
package cn.nukkit.level.format.anvil;

import cn.nukkit.Player;
import cn.nukkit.api.PowerNukkitXOnly;
import cn.nukkit.api.Since;
import cn.nukkit.api.UsedByReflection;
import cn.nukkit.block.Block;
import cn.nukkit.blockentity.BlockEntity;
import cn.nukkit.entity.Entity;
import cn.nukkit.level.DimensionData;
import cn.nukkit.level.Level;
import cn.nukkit.level.biome.Biome;
import cn.nukkit.level.format.ChunkSection3DBiome;
import cn.nukkit.level.format.LevelProvider;
import cn.nukkit.level.format.anvil.palette.BiomePalette;
import cn.nukkit.level.format.generic.BaseChunk;
import cn.nukkit.level.format.generic.EmptyChunkSection;
import cn.nukkit.nbt.NBTIO;
import cn.nukkit.nbt.tag.*;
import cn.nukkit.utils.BinaryStream;
import cn.nukkit.utils.BlockUpdateEntry;
import cn.nukkit.utils.ChunkException;
import cn.nukkit.utils.Zlib;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.Nullable;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.Constructor;
import java.nio.ByteOrder;
import java.util.*;

/**
 * @author MagicDroidX (Nukkit Project)
 */
@Log4j2
public class Chunk extends BaseChunk {

    protected long inhabitedTime;
    protected boolean terrainPopulated;
    protected boolean terrainGenerated;
    protected boolean isNew384World = false;
    @PowerNukkitXOnly
    @Since("1.19.20-r4")
    protected DimensionData dimensionData = null;

    public Chunk(LevelProvider level) {
        this(level, (CompoundTag) null);
    }

    @PowerNukkitXOnly
    @Since("1.19.20-r4")
    public Chunk(LevelProvider level, DimensionData dimensionData) {
        this(level, null, dimensionData);
    }

    public Chunk(Class providerClass) {
        this((LevelProvider) null, (CompoundTag) null);
        this.providerClass = providerClass;
    }

    @PowerNukkitXOnly
    @Since("1.19.20-r4")
    public Chunk(Class providerClass, DimensionData dimensionData) {
        this((LevelProvider) null, null, dimensionData);
        this.providerClass = providerClass;
    }

    public Chunk(Class providerClass, CompoundTag nbt) {
        this((LevelProvider) null, nbt);
        this.providerClass = providerClass;
    }

    @PowerNukkitXOnly
    @Since("1.19.20-r4")
    public Chunk(Class providerClass, CompoundTag nbt, DimensionData dimensionData) {
        this((LevelProvider) null, nbt, dimensionData);
        this.providerClass = providerClass;
    }

    public Chunk(LevelProvider level, CompoundTag nbt) {
        this(level, nbt, null);
    }

    @PowerNukkitXOnly
    @Since("1.19.20-r4")
    public Chunk(LevelProvider level, CompoundTag nbt, DimensionData dimensionData) {
        this.provider = level;
        if (level != null) {
            if (level.getLevel() != null) {
                this.dimensionData = level.getLevel().getDimensionData();
            }
            this.providerClass = level.getClass();
        }
        if (dimensionData != null) {
            this.dimensionData = dimensionData;
        }

        this.sections = new cn.nukkit.level.format.ChunkSection[getChunkSectionCount()];
        this.sectionLength = getChunkSectionCount();
        for (int i = 0; i < sectionLength; i++) {
            sections[i] = new EmptyChunkSection(i);
        }
        if (nbt == null) {
            this.biomes = new byte[16 * 16];
            this.heightMap = new byte[256];
            this.NBTentities = new ArrayList<>(0);
            this.NBTtiles = new ArrayList<>(0);
            return;
        }

        if (nbt.contains("BiomeColors")) {
            this.biomes = new byte[16 * 16];
            int[] biomeColors = nbt.getIntArray("BiomeColors");
            if (biomeColors != null && biomeColors.length == 256) {
                BiomePalette palette = new BiomePalette(biomeColors);
                for (int x = 0; x < 16; x++) {
                    for (int z = 0; z < 16; z++) {
                        this.biomes[(x << 4) | z] = (byte) (palette.get(x, z) >> 24);
                    }
                }
            }
        } else {
            this.biomes = Arrays.copyOf(nbt.getByteArray("Biomes"), 256);
        }

        for (Tag section : nbt.getList("Sections").getAll()) {
            if (section instanceof CompoundTag compoundTag) {
                int version = compoundTag.getByte("Version");
                int y = compoundTag.getByte("Y");
                if (y < getChunkSectionCount()) {
                    if (version == -1) {
                        var biomeId = compoundTag.getByteArray("Biomes");
                        if (biomeId != null) {
                            sections[y] = new EmptyChunkSection(y, biomeId);
                        } else {
                            sections[y] = new EmptyChunkSection(y);
                        }
                    } else {
                        final ChunkSection chunkSection = new ChunkSection((CompoundTag) section, this.biomes);
                        if (chunkSection.hasBlocks()) {
                            sections[y] = chunkSection;
                            if (chunkSection.invalidCustomBlockWhenLoad) {
                                this.setChanged();
                            }
                        } else {
                            sections[y] = new EmptyChunkSection(y);
                        }
                    }
                }

            }
        }

        Map extraData = new HashMap<>();

        Tag extra = nbt.get("ExtraData");
        if (extra instanceof ByteArrayTag) {
            BinaryStream stream = new BinaryStream(((ByteArrayTag) extra).data);
            for (int i = 0; i < stream.getInt(); i++) {
                int key = stream.getInt();
                extraData.put(key, stream.getShort());
            }
        }

        this.setPosition(nbt.getInt("xPos"), nbt.getInt("zPos"));
        if (sections.length > getChunkSectionCount()) {
            throw new ChunkException("Invalid amount of chunks");
        }

        int[] heightMap = nbt.getIntArray("HeightMap");
        this.heightMap = new byte[256];
        if (heightMap.length != 256) {
            Arrays.fill(this.heightMap, (byte) 255);
        } else {
            for (int i = 0; i < heightMap.length; i++) {
                this.heightMap[i] = (byte) heightMap[i];
            }
        }

        if (!extraData.isEmpty()) this.extraData = extraData;

        this.NBTentities = nbt.getList("Entities", CompoundTag.class).getAll();
        this.NBTtiles = nbt.getList("TileEntities", CompoundTag.class).getAll();
        if (this.NBTentities.isEmpty()) this.NBTentities = null;
        if (this.NBTtiles.isEmpty()) this.NBTtiles = null;

        ListTag updateEntries = nbt.getList("TileTicks", CompoundTag.class);

        if (updateEntries != null && updateEntries.size() > 0) {
            for (CompoundTag entryNBT : updateEntries.getAll()) {
                Block block = null;

                try {
                    Tag tag = entryNBT.get("i");
                    if (tag instanceof StringTag) {
                        String name = ((StringTag) tag).data;

                        @SuppressWarnings("unchecked")
                        Class clazz = (Class) Class.forName("cn.nukkit.block." + name);

                        Constructor constructor = clazz.getDeclaredConstructor();
                        constructor.setAccessible(true);
                        block = (Block) constructor.newInstance();
                    }
                } catch (Throwable e) {
                    continue;
                }

                if (block == null) {
                    continue;
                }

                block.x = entryNBT.getInt("x");
                block.y = entryNBT.getInt("y");
                block.z = entryNBT.getInt("z");
                block.layer = 0;

                this.provider.getLevel().scheduleUpdate(block, block, entryNBT.getInt("t"), entryNBT.getInt("p"), false);
            }
        }

        this.inhabitedTime = nbt.getLong("InhabitedTime");
        this.terrainPopulated = nbt.getBoolean("TerrainPopulated");
        this.terrainGenerated = nbt.getBoolean("TerrainGenerated");
        if (nbt.contains("isNew384World")) {
            this.isNew384World = nbt.getBoolean("isNew384World");
        }
    }

    public static Chunk fromBinary(byte[] data) {
        return fromBinary(data, null);
    }

    public static Chunk fromBinary(byte[] data, LevelProvider provider) {
        try {
            CompoundTag chunk = NBTIO.read(new ByteArrayInputStream(Zlib.inflate(data)), ByteOrder.BIG_ENDIAN);

            if (!chunk.contains("Level") || !(chunk.get("Level") instanceof CompoundTag)) {
                return null;
            }

            return new Chunk(provider, chunk.getCompound("Level"));
        } catch (Exception e) {
            log.error("An error has occurred while parsing a chunk from {}", provider.getName(), e);
            return null;
        }
    }

    public static Chunk fromFastBinary(byte[] data) {
        return fromFastBinary(data, null);
    }

    public static Chunk fromFastBinary(byte[] data, LevelProvider provider) {
        try {
            CompoundTag chunk = NBTIO.read(new DataInputStream(new ByteArrayInputStream(data)), ByteOrder.BIG_ENDIAN);
            if (!chunk.contains("Level") || !(chunk.get("Level") instanceof CompoundTag)) {
                return null;
            }

            return new Chunk(provider, chunk.getCompound("Level"));
        } catch (Exception e) {
            return null;
        }
    }

    @Nullable
    @UsedByReflection
    public static Chunk getEmptyChunk(int chunkX, int chunkZ) {
        return getEmptyChunk(chunkX, chunkZ, (LevelProvider) null);
    }

    @PowerNukkitXOnly
    @Since("1.19.20-r4")
    @Nullable
    @UsedByReflection
    public static Chunk getEmptyChunk(int chunkX, int chunkZ, DimensionData dimensionData) {
        return getEmptyChunk(chunkX, chunkZ, null, dimensionData);
    }

    @Nullable
    public static Chunk getEmptyChunk(int chunkX, int chunkZ, LevelProvider provider) {
        return getEmptyChunk(chunkX, chunkZ, provider, null);
    }

    @PowerNukkitXOnly
    @Since("1.19.20-r4")
    @Nullable
    public static Chunk getEmptyChunk(int chunkX, int chunkZ, LevelProvider provider, DimensionData dimensionData) {
        try {
            Chunk chunk;
            if (provider != null) {
                chunk = new Chunk(provider, null, dimensionData);
            } else {
                chunk = new Chunk(Anvil.class, null, dimensionData);
            }

            chunk.setPosition(chunkX, chunkZ);

            chunk.heightMap = new byte[256];
            chunk.inhabitedTime = 0;
            chunk.terrainGenerated = false;
            chunk.terrainPopulated = false;
            if (provider != null)
                chunk.isNew384World = provider.isOverWorld();
//            chunk.lightPopulated = false;
            return chunk;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public Chunk clone() {
        return (Chunk) super.clone();
    }

    @PowerNukkitXOnly
    @Since("1.19.20-r5")
    @Override
    protected void createChunkSection(int sectionY) {
        setInternalSection(sectionY, new ChunkSection(sectionY));
    }

    @Since("1.19.20-r4")
    @Override
    public int getMaxHeight() {
        if (dimensionData != null) {
            return dimensionData.getMaxHeight() + 1;
        }
        return getChunkSectionCount() == 24 ? 320 : 256;
    }

    @Since("1.19.20-r4")
    @Override
    public int getMinHeight() {
        if (dimensionData != null) {
            return dimensionData.getMinHeight();
        }
        return getChunkSectionCount() == 24 ? -64 : 0;
    }

    @Since("1.19.20-r3")
    @Override
    public boolean isChunkSection3DBiomeSupported() {
        return true;
    }

    @Override
    public boolean isPopulated() {
        return this.terrainPopulated;
    }

    @Override
    public void setPopulated(boolean value) {
        if (value != this.terrainPopulated) {
            this.terrainPopulated = value;
            setChanged();
        }
    }

    @Override
    public void setPopulated() {
        this.setPopulated(true);
    }

    @Override
    public boolean isGenerated() {
        return this.terrainGenerated || this.terrainPopulated;
    }

    @Override
    public void setGenerated(boolean value) {
        if (this.terrainGenerated != value) {
            this.terrainGenerated = value;
            setChanged();
        }
    }

    @Override
    public void setGenerated() {
        this.setGenerated(true);
    }

    @Override
    public int getBiomeId(int x, int z) {
        return getBiomeId(x, 70, z);
    }

    @Override
    public void setBiomeId(int x, int z, byte biomeId) {
        for (var section : sections) {
            if (section instanceof ChunkSection3DBiome chunkSection3DBiome) {
                for (int dy = 0; dy < 16; dy++) {
                    chunkSection3DBiome.setBiomeId(x, dy, z, biomeId);
                }
            }
        }
        this.biomes[(x << 4) | z] = biomeId;
        this.setChanged();
    }

    @Override
    public int getBiomeId(int x, int y, int z) {
        if (getOrCreateMutableSection(toSectionY(y)) instanceof ChunkSection3DBiome chunkSection3DBiome) {
            return chunkSection3DBiome.getBiomeId(x, y & 0xf, z) & 0xFF;
        }
        return super.getBiomeId(x, z);
    }

    @Override
    public void setBiomeId(int x, int y, int z, byte biomeId) {
        if (getOrCreateMutableSection(toSectionY(y)) instanceof ChunkSection3DBiome chunkSection3DBiome) {
            chunkSection3DBiome.setBiomeId(x, y & 0xf, z, biomeId);
            this.setChanged();
            return;
        }
        this.biomes[(x << 4) | z] = biomeId;
        this.setChanged();
    }

    @Override
    public void setBiome(int x, int y, int z, Biome biome) {
        setBiomeId(x, y, z, (byte) biome.getId());
    }

    public CompoundTag getNBT() {
        CompoundTag tag = new CompoundTag();

        tag.put("LightPopulated", new ByteTag("LightPopulated", (byte) (isLightPopulated() ? 1 : 0)));
        tag.put("InhabitedTime", new LongTag("InhabitedTime", this.inhabitedTime));

        tag.put("V", new ByteTag("V", (byte) 1));

        tag.put("TerrainGenerated", new ByteTag("TerrainGenerated", (byte) (isGenerated() ? 1 : 0)));
        tag.put("TerrainPopulated", new ByteTag("TerrainPopulated", (byte) (isPopulated() ? 1 : 0)));
        tag.putBoolean("isNew384World", this.isNew384World);

        return tag;
    }

    @Override
    public byte[] toFastBinary() {
        CompoundTag nbt = this.getNBT().copy();
        nbt.remove("BiomeColors");

        nbt.putInt("xPos", this.getX());
        nbt.putInt("zPos", this.getZ());

        nbt.putByteArray("Biomes", this.getBiomeIdArray());
        int[] heightInts = new int[256];
        byte[] heightBytes = this.getHeightMapArray();
        for (int i = 0; i < heightInts.length; i++) {
            heightInts[i] = heightBytes[i] & 0xFF;
        }

        for (cn.nukkit.level.format.ChunkSection section : this.getSections()) {
            CompoundTag s = section.toNBT();
            nbt.getList("Sections", CompoundTag.class).add(s);
        }

        ArrayList entities = new ArrayList<>();
        for (Entity entity : this.getEntities().values()) {
            if (!(entity instanceof Player) && !entity.closed) {
                entity.saveNBT();
                entities.add(entity.namedTag);
            }
        }
        ListTag entityListTag = new ListTag<>("Entities");
        entityListTag.setAll(entities);
        nbt.putList(entityListTag);

        ArrayList tiles = new ArrayList<>();
        for (BlockEntity blockEntity : this.getBlockEntities().values()) {
            blockEntity.saveNBT();
            tiles.add(blockEntity.namedTag);
        }
        ListTag tileListTag = new ListTag<>("TileEntities");
        tileListTag.setAll(tiles);
        nbt.putList(tileListTag);

        Set entries = this.provider.getLevel().getPendingBlockUpdates(this);

        if (entries != null) {
            ListTag tileTickTag = new ListTag<>("TileTicks");
            long totalTime = this.provider.getLevel().getCurrentTick();

            for (BlockUpdateEntry entry : entries) {
                CompoundTag entryNBT = new CompoundTag()
                        .putString("i", entry.block.getSaveId())
                        .putInt("x", entry.pos.getFloorX())
                        .putInt("y", entry.pos.getFloorY())
                        .putInt("z", entry.pos.getFloorZ())
                        .putInt("t", (int) (entry.delay - totalTime))
                        .putInt("p", entry.priority);
                tileTickTag.add(entryNBT);
            }

            nbt.putList(tileTickTag);
        }

        BinaryStream extraData = new BinaryStream();
        Map extraDataArray = this.getBlockExtraDataArray();
        extraData.putInt(extraDataArray.size());
        for (Integer key : extraDataArray.keySet()) {
            extraData.putInt(key);
            extraData.putShort(extraDataArray.get(key));
        }

        nbt.putByteArray("ExtraData", extraData.getBuffer());

        CompoundTag chunk = new CompoundTag("");
        chunk.putCompound("Level", nbt);

        try {
            return NBTIO.write(chunk, ByteOrder.BIG_ENDIAN);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }

    }

    @Override
    public byte[] toBinary() {
        CompoundTag nbt = this.getNBT().copy();
        nbt.remove("BiomeColors");

        nbt.putInt("xPos", this.getX());
        nbt.putInt("zPos", this.getZ());

        ListTag sectionList = new ListTag<>("Sections");
        for (cn.nukkit.level.format.ChunkSection section : this.getSections()) {
            sectionList.add(section.toNBT());
        }
        nbt.putList(sectionList);

        nbt.putByteArray("Biomes", this.getBiomeIdArray());
        int[] heightInts = new int[256];
        byte[] heightBytes = this.getHeightMapArray();
        for (int i = 0; i < heightInts.length; i++) {
            heightInts[i] = heightBytes[i] & 0xFF;
        }
        nbt.putIntArray("HeightMap", heightInts);

        ArrayList entities = new ArrayList<>();
        for (Entity entity : this.getEntities().values()) {
            if (!(entity instanceof Player) && !entity.closed) {
                entity.saveNBT();
                entities.add(entity.namedTag);
            }
        }
        ListTag entityListTag = new ListTag<>("Entities");
        entityListTag.setAll(entities);
        nbt.putList(entityListTag);

        ArrayList tiles = new ArrayList<>();
        for (BlockEntity blockEntity : this.getBlockEntities().values()) {
            blockEntity.saveNBT();
            tiles.add(blockEntity.namedTag);
        }
        ListTag tileListTag = new ListTag<>("TileEntities");
        tileListTag.setAll(tiles);
        nbt.putList(tileListTag);

        Set entries = null;
        if (this.provider != null) {
            Level level = provider.getLevel();
            if (level != null) {
                entries = level.getPendingBlockUpdates(this);
            }
        }

        if (entries != null) {
            ListTag tileTickTag = new ListTag<>("TileTicks");
            long totalTime = this.provider.getLevel().getCurrentTick();

            for (BlockUpdateEntry entry : entries) {
                CompoundTag entryNBT = new CompoundTag()
                        .putString("i", entry.block.getSaveId())
                        .putInt("x", entry.pos.getFloorX())
                        .putInt("y", entry.pos.getFloorY())
                        .putInt("z", entry.pos.getFloorZ())
                        .putInt("t", (int) (entry.delay - totalTime))
                        .putInt("p", entry.priority);
                tileTickTag.add(entryNBT);
            }

            nbt.putList(tileTickTag);
        }

        BinaryStream extraData = new BinaryStream();
        Map extraDataArray = this.getBlockExtraDataArray();
        extraData.putInt(extraDataArray.size());
        for (Integer key : extraDataArray.keySet()) {
            extraData.putInt(key);
            extraData.putShort(extraDataArray.get(key));
        }

        nbt.putByteArray("ExtraData", extraData.getBuffer());

        CompoundTag chunk = new CompoundTag("");
        chunk.putCompound("Level", nbt);
        try {
            return Zlib.deflate(NBTIO.write(chunk, ByteOrder.BIG_ENDIAN), RegionLoader.COMPRESSION_LEVEL);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    @Override
    public int getBlockSkyLight(int x, int y, int z) {
        cn.nukkit.level.format.ChunkSection section = this.sections[toSectionY(y)];
        if (section instanceof ChunkSection anvilSection) {
            if (anvilSection.skyLight != null) {
                return section.getBlockSkyLight(x, y & 0x0f, z);
            } else if (!anvilSection.hasSkyLight) {
                return 0;
            } else {
                int height = getHighestBlockAt(x, z);
                if (height < y) {
                    return 15;
                } else if (height == y) {
                    return Block.isTransparent(getBlockId(x, y, z)) ? 15 : 0;
                } else {
                    return section.getBlockSkyLight(x, y & 0x0f, z);
                }
            }
        } else {
            return section.getBlockSkyLight(x, y & 0x0f, z);
        }
    }

    @Override
    public int getBlockLight(int x, int y, int z) {
        cn.nukkit.level.format.ChunkSection section = this.sections[toSectionY(y)];
        if (section instanceof ChunkSection anvilSection) {
            if (anvilSection.blockLight != null) {
                return section.getBlockLight(x, y & 0x0f, z);
            } else if (!anvilSection.hasBlockLight) {
                return 0;
            } else {
                return section.getBlockLight(x, y & 0x0f, z);
            }
        } else {
            return section.getBlockLight(x, y & 0x0f, z);
        }
    }

    @Override
    public boolean compress() {
        super.compress();
        boolean result = false;
        for (cn.nukkit.level.format.ChunkSection section : getSections()) {
            if (section instanceof ChunkSection anvilSection) {
                if (!anvilSection.isEmpty()) {
                    result |= anvilSection.compress();
                }
            }
        }
        return result;
    }

    @Since("1.19.21-r1")
    @Override
    public void reObfuscateChunk() {
        for (var section : getSections()) {
            section.setNeedReObfuscate();
        }
    }

    @PowerNukkitXOnly
    @Since("1.19.20-r4")
    @Override
    public int getChunkSectionCount() {
        if (dimensionData != null) return dimensionData.getChunkSectionCount();
        return super.getChunkSectionCount();
    }

    @PowerNukkitXOnly
    @Since("1.19.20-r4")
    @Override
    public boolean isOverWorld() {
        if (dimensionData != null) {
            return dimensionData.getDimensionId() == Level.DIMENSION_OVERWORLD;
        }
        return super.isOverWorld();
    }

    @PowerNukkitXOnly
    @Since("1.19.20-r4")
    @Override
    public boolean isNether() {
        if (dimensionData != null) {
            return dimensionData.getDimensionId() == Level.DIMENSION_NETHER;
        }
        return super.isNether();
    }

    @PowerNukkitXOnly
    @Since("1.19.20-r4")
    @Override
    public boolean isTheEnd() {
        if (dimensionData != null) {
            return dimensionData.getDimensionId() == Level.DIMENSION_THE_END;
        }
        return super.isTheEnd();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy