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

cn.nukkit.level.format.anvil.util.BlockStorage Maven / Gradle / Ivy

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

import cn.nukkit.api.*;
import cn.nukkit.block.Block;
import cn.nukkit.block.BlockID;
import cn.nukkit.blockstate.BlockState;
import cn.nukkit.blockstate.BlockStateRegistry;
import cn.nukkit.blockstate.exception.InvalidBlockStateException;
import cn.nukkit.level.Level;
import cn.nukkit.level.util.PalettedBlockStorage;
import cn.nukkit.math.NukkitRandom;
import cn.nukkit.utils.BinaryStream;
import cn.nukkit.utils.functional.BlockPositionDataConsumer;
import com.google.common.base.Preconditions;
import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.extern.log4j.Log4j2;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.Arrays;
import java.util.BitSet;

import static cn.nukkit.api.API.Definition.INTERNAL;
import static cn.nukkit.api.API.Usage.BLEEDING;

@ParametersAreNonnullByDefault
@Log4j2
public class BlockStorage {
    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public static final BlockStorage[] EMPTY_ARRAY = new BlockStorage[0];
    @PowerNukkitOnly
    public static final int SECTION_SIZE = 4096;
    private static final byte FLAG_HAS_ID = 0b00_0001;
    private static final byte FLAG_HAS_ID_EXTRA = 0b00_0010;
    private static final byte FLAG_HAS_DATA_EXTRA = 0b00_0100;
    private static final byte FLAG_HAS_DATA_BIG = 0b00_1000;
    private static final byte FLAG_HAS_DATA_HUGE = 0b01_0000;
    private static final byte FLAG_PALETTE_UPDATED = 0b10_0000;
    private static final byte FLAG_ENABLE_ID_EXTRA = FLAG_HAS_ID | FLAG_HAS_ID_EXTRA;
    private static final byte FLAG_ENABLE_DATA_EXTRA = FLAG_HAS_ID | FLAG_HAS_DATA_EXTRA;
    private static final byte FLAG_ENABLE_DATA_BIG = FLAG_ENABLE_DATA_EXTRA | FLAG_HAS_DATA_BIG;
    private static final byte FLAG_ENABLE_DATA_HUGE = FLAG_ENABLE_DATA_BIG | FLAG_HAS_DATA_HUGE;
    private static final byte FLAG_EVERYTHING_ENABLED = FLAG_ENABLE_DATA_HUGE | FLAG_ENABLE_ID_EXTRA | FLAG_PALETTE_UPDATED;
    private static final int BLOCK_ID_MASK = 0x00FF;
    private static final int BLOCK_ID_EXTRA_MASK = 0xFF00;
    private static final int BLOCK_ID_FULL = BLOCK_ID_MASK | BLOCK_ID_EXTRA_MASK;
    private static final BlockState[] EMPTY = new BlockState[SECTION_SIZE];

    static {
        Arrays.fill(EMPTY, BlockState.AIR);
    }

    private final PalettedBlockStorage palette;
    private final BlockState[] states;
    private byte flags = FLAG_PALETTE_UPDATED;
    @Nullable
    private BitSet denyStates = null;
    @PowerNukkitXOnly
    @Since("1.19.21-r1")
    private boolean needReObfuscate = true;

    public BlockStorage() {
        states = EMPTY.clone();
        palette = new PalettedBlockStorage();
    }

    @Since("1.4.0.0-PN")
    @API(definition = INTERNAL, usage = BLEEDING)
    BlockStorage(BlockState[] states, byte flags, PalettedBlockStorage palette, @Nullable BitSet denyStates) {
        this.states = states;
        this.flags = flags;
        this.palette = palette;
        this.denyStates = denyStates;
    }

    private static int getIndex(int x, int y, int z) {
        checkArg(x, "x");
        checkArg(y, "y");
        checkArg(z, "z");
        int index = (x << 8) + (z << 4) + y; // XZY = Bedrock format
        Preconditions.checkArgument(index >= 0 && index < SECTION_SIZE, "Invalid index");
        return index;
    }

    private static void checkArg(int pos, String arg) {
        try {
            Preconditions.checkElementIndex(pos, 16, arg);
        } catch (IndexOutOfBoundsException e) {
            throw new IllegalArgumentException(e);
        }
    }

    @Deprecated
    @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.4.0.0-PN")
    @Nonnegative
    public int getBlockData(int x, int y, int z) {
        return states[getIndex(x, y, z)].getSignedBigDamage();
    }

    @Nonnegative
    public int getBlockId(int x, int y, int z) {
        return states[getIndex(x, y, z)].getBlockId();
    }

    public void setBlockId(int x, int y, int z, @Nonnegative int id) {
        int index = getIndex(x, y, z);
        setBlockState(index, states[index].withBlockId(id));
    }

    @Deprecated
    @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.4.0.0-PN")
    public void setBlockData(int x, int y, int z, @Nonnegative int data) {
        int index = getIndex(x, y, z);
        setBlockState(index, states[index].withData(data));
    }

    @Deprecated
    @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.4.0.0-PN")
    @PowerNukkitOnly
    @Since("1.3.0.0-PN")
    public void setBlock(int x, int y, int z, @Nonnegative int id, @Nonnegative int data) {
        int index = getIndex(x, y, z);
        BlockState state = BlockState.of(id, data);
        setBlockState(index, state);
    }

    @Deprecated
    @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.3.0.0-PN")
    public int getFullBlock(int x, int y, int z) {
        return getFullBlock(getIndex(x, y, z));
    }

    @Deprecated
    @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.3.0.0-PN")
    public void setFullBlock(int x, int y, int z, @Nonnegative int value) {
        this.setFullBlock(getIndex(x, y, z), value);
    }

    @PowerNukkitOnly
    @Since("1.3.0.0-PN")
    public BlockState getAndSetBlock(int x, int y, int z, @Nonnegative int id, @Nonnegative int meta) {
        return setBlockState(getIndex(x, y, z), BlockState.of(id, meta));
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public BlockState getAndSetBlockState(int x, int y, int z, BlockState state) {
        return setBlockState(getIndex(x, y, z), state);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public void setBlockState(int x, int y, int z, BlockState state) {
        setBlockState(getIndex(x, y, z), state);
    }

    @Deprecated
    @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.3.0.0-PN", replaceWith = "getAndSetFullBlock")
    public int getAndSetFullBlock(int x, int y, int z, int value) {
        return getAndSetFullBlock(getIndex(x, y, z), value);
    }

    @Deprecated
    @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.3.0.0-PN")
    private int getAndSetFullBlock(int index, int value) {
        Preconditions.checkArgument(value < (0x1FF << Block.DATA_BITS | Block.DATA_MASK), "Invalid full block");
        int blockId = value >> Block.DATA_BITS & BLOCK_ID_FULL;
        int data = value & Block.DATA_MASK;
        BlockState newState = BlockState.of(blockId, data);
        BlockState oldState = states[index];
        if (oldState.equals(newState)) {
            return value;
        }
        setBlockState(index, newState);
        return oldState.getFullId();
    }

    @Deprecated
    @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.3.0.0-PN")
    private int getFullBlock(int index) {
        return states[index].getFullId();
    }

    @Deprecated
    @DeprecationDetails(reason = "The meta is limited to 32 bits", since = "1.3.0.0-PN")
    private void setFullBlock(int index, int value) {
        Preconditions.checkArgument(value < (BLOCK_ID_FULL << Block.DATA_BITS | Block.DATA_MASK), "Invalid full block");
        int blockId = value >> Block.DATA_BITS & BLOCK_ID_FULL;
        int data = value & Block.DATA_MASK;
        BlockState state = BlockState.of(blockId, data);
        setBlockState(index, state);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    protected BlockState setBlockState(int index, BlockState state) {
        if (log.isTraceEnabled() && !state.isCachedValidationValid()) {
            try {
                int runtimeId = state.getBlock().getRuntimeId();
                if (runtimeId == BlockStateRegistry.getFallbackRuntimeId()) {
                    log.trace("Setting a state that will become info update! State: {}", state, new RuntimeException());
                }
            } catch (InvalidBlockStateException e) {
                log.trace("Setting an invalid state! State: {}", state, e);
            }
        }

        BlockState previous = states[index];
        if (previous.equals(state)) {
            return previous;
        }

        states[index] = state;
        updateFlags(index, previous, state);
        if (getFlag(FLAG_PALETTE_UPDATED)) {
            int runtimeId = state.getRuntimeId();
            if (runtimeId == BlockStateRegistry.getFallbackRuntimeId() && !state.equals(BlockStateRegistry.getFallbackBlockState())) {
                delayPaletteUpdates();
            } else {
                palette.setBlock(index, runtimeId);
            }
        }

        return previous;
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public void delayPaletteUpdates() {
        setFlag(FLAG_PALETTE_UPDATED, false);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public boolean isPaletteUpdateDelayed() {
        return !getFlag(FLAG_PALETTE_UPDATED);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public BlockState getBlockState(int x, int y, int z) {
        return states[getIndex(x, y, z)];
    }

    @Since("1.4.0.0-PN")
    @PowerNukkitOnly
    public void recheckBlocks() {
        flags = computeFlags((byte) (flags & FLAG_PALETTE_UPDATED), states);
    }

    private void updateFlags(int index, BlockState previous, BlockState state) {
        if (flags != FLAG_EVERYTHING_ENABLED) {
            flags = computeFlags(flags, state);
        }

        if (denyStates != null) {
            switch (previous.getBlockId()) {
                case BlockID.DENY:
                    clearDeny(index);
                    break;
                case BlockID.ALLOW:
                    clearAllow(index);
                    break;
                case BlockID.BORDER_BLOCK:
                    clearBorder(index);
                    break;
                default:
            }
        }

        switch (state.getBlockId()) {
            case BlockID.DENY:
                deny(index);
                break;
            case BlockID.BORDER_BLOCK:
                border(index);
                break;
            case BlockID.ALLOW:
                allow(index);
                break;
            default:
        }
    }

    private void border(int index) {
        if (denyStates == null) {
            denyStates = new BitSet();
        }
        index = (index & ~0xF) << 1;
        for (int y = 0; y <= 0xF; y++) {
            denyStates.set(index++);    // Deny
            denyStates.set(index++);    // Allow
            // Both deny and allow means border
        }
    }

    private void deny(int index) {
        if (denyStates == null) {
            denyStates = new BitSet();
        }
        int y = index & 0xF;
        index <<= 1;
        boolean first = true;
        for (; y <= 0xF; y++, index += 2, first = false) {
            if (denyStates.get(index + 1)) { //Allow
                if (first) { // Replacing an allow block with a deny block
                    if (denyStates.get(index)) { // If the XZ pos have a border, the border takes priority
                        return;
                    }
                    denyStates.clear(index + 1);
                } else if (states[index >> 1].getBlockId() == BlockID.ALLOW) {
                    // Check if the allow state is actually from a allow block or from a previous removal
                    return;
                } else {
                    denyStates.clear(index + 1);
                }
            }

            denyStates.set(index);
        }
    }

    private void allow(int index) {
        if (denyStates == null) {
            denyStates = new BitSet();
        }
        int y = index & 0xF;
        index <<= 1;
        boolean first = true;
        for (; y <= 0xF; y++, index += 2, first = false) {
            if (denyStates.get(index)) { //Deny
                if (first) { // Replacing a deny block with an allow block
                    if (denyStates.get(index + 1)) { // If the XZ pos have a border, the border takes priority
                        return;
                    }
                    denyStates.clear(index);
                } else if (states[index >> 1].getBlockId() == BlockID.DENY) {
                    // Check if the deny state is actually from a deny block or from a previous removal
                    return;
                } else {
                    denyStates.clear(index);
                }
            }

            denyStates.set(index + 1);
        }
    }

    private void clearAllow(int index) {
        assert denyStates != null;
        int y = index & 0xF;
        index <<= 1;
        for (; y <= 0xF; y++, index += 2, index++) {
            if (denyStates.get(index)) { // Deny or border
                break;
            }

            denyStates.clear(index + 1); // Remove the allow
        }
    }

    private void clearDeny(int index) {
        assert denyStates != null;
        int y = index & 0xF;
        index <<= 1;
        for (; y <= 0xF; y++, index += 2, index++) {
            if (denyStates.get(index + 1)) { // Allow or border
                break;
            }

            denyStates.clear(index); // Remove the deny
        }
    }

    private void clearBorder(final int index) {
        assert denyStates != null;

        // Check if there's an other border
        final int bottomIndex = index & ~0xF;
        final int topIndex = index | 0xF;
        for (int blockIndex = bottomIndex; blockIndex < topIndex; blockIndex++) {
            if (states[blockIndex].getBlockId() == BlockID.BORDER_BLOCK) {
                return;
            }
        }

        // Clear the border flags
        boolean removeDeny = true;
        boolean removeAllow = true;
        for (int blockIndex = bottomIndex, flagIndex = blockIndex << 1; blockIndex < topIndex; blockIndex++, flagIndex += 2) {
            switch (states[blockIndex].getBlockId()) {
                case BlockID.ALLOW:
                    removeDeny = true;
                    removeAllow = false;
                    break;
                case BlockID.DENY:
                    removeDeny = false;
                    removeAllow = true;
                    break;
                default:
            }
            if (removeDeny) {
                denyStates.clear(flagIndex);
            }
            if (removeAllow) {
                denyStates.clear(flagIndex + 1);
            }
        }
    }

    private byte computeFlags(byte newFlags, BlockState... states) {
        for (BlockState state : states) {
            int blockId = state.getBlockId();
            if ((blockId & BLOCK_ID_EXTRA_MASK) != 0) {
                newFlags |= FLAG_ENABLE_ID_EXTRA;
            } else if (blockId != 0) {
                newFlags |= FLAG_HAS_ID;
            }

            int bitSize = state.getBitSize();
            if (bitSize > 16) {
                newFlags |= FLAG_ENABLE_DATA_HUGE;
            } else if (bitSize > 8) {
                newFlags |= FLAG_ENABLE_DATA_BIG;
            } else if (bitSize > 4) {
                newFlags |= FLAG_ENABLE_DATA_EXTRA;
            } else if (bitSize > 1 || blockId != 0) {
                newFlags |= FLAG_HAS_ID;
            }

            if (newFlags == FLAG_EVERYTHING_ENABLED) {
                return newFlags;
            }
        }

        return newFlags;
    }

    @Since("1.4.0.0-PN")
    public BlockStorage copy() {
        BitSet deny = denyStates;
        return new BlockStorage(states.clone(), flags, palette.copy(), (BitSet) (deny != null ? deny.clone() : null));
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @Nonnull
    public ImmutableBlockStorage immutableCopy() {
        return new ImmutableBlockStorage(states, flags, palette, denyStates);
    }

    private boolean getFlag(byte flag) {
        return (flags & flag) == flag;
    }

    private void setFlag(byte flag, boolean value) {
        if (value) {
            flags |= flag;
        } else {
            flags &= ~flag;
        }
    }

    @PowerNukkitOnly
    public boolean hasBlockIds() {
        return getFlag(FLAG_HAS_ID);
    }

    @PowerNukkitOnly
    public boolean hasBlockIdExtras() {
        return getFlag(FLAG_HAS_ID_EXTRA);
    }

    @PowerNukkitOnly
    public boolean hasBlockDataExtras() {
        return getFlag(FLAG_HAS_DATA_EXTRA);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public boolean hasBlockDataBig() {
        return getFlag(FLAG_HAS_DATA_BIG);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public boolean hasBlockDataHuge() {
        return getFlag(FLAG_HAS_DATA_HUGE);
    }

    private boolean isPaletteUpdated() {
        return getFlag(FLAG_PALETTE_UPDATED);
    }

    @Since("1.4.0.0-PN")
    public void writeTo(BinaryStream stream) {
        if (!isPaletteUpdated()) {
            for (int i = 0; i < states.length; i++) {
                palette.setBlock(i, states[i].getRuntimeId());
            }
            setFlag(FLAG_PALETTE_UPDATED, true);
        }
        palette.writeTo(stream);
    }

    protected final boolean canBeObfuscated(IntSet transparentBlockSet, int x, int y, int z) {
        return !transparentBlockSet.contains(states[getIndex(x + 1, y, z)].getRuntimeId()) &&
                !transparentBlockSet.contains(states[getIndex(x - 1, y, z)].getRuntimeId()) &&
                !transparentBlockSet.contains(states[getIndex(x, y + 1, z)].getRuntimeId()) &&
                !transparentBlockSet.contains(states[getIndex(x, y - 1, z)].getRuntimeId()) &&
                !transparentBlockSet.contains(states[getIndex(x, y, z + 1)].getRuntimeId()) &&
                !transparentBlockSet.contains(states[getIndex(x, y, z - 1)].getRuntimeId());
    }

    @PowerNukkitXOnly
    @Since("1.19.21-r1")
    public void writeObfuscatedTo(BinaryStream stream, Level level) {
        var realOreToFakeMap = level.getRawRealOreToReplacedRuntimeIdMap();
        var fakeBlockMap = level.getRawFakeOreToPutRuntimeIdMap();
        var transparentBlockSet = Level.getRawTransparentBlockRuntimeIds();
        var XAndDenominator = level.getFakeOreDenominator() - 1;
        var nukkitRandom = new NukkitRandom(level.getSeed());
        if (!isPaletteUpdated() || needReObfuscate) {
            for (int i = 0; i < states.length; i++) {
                int x = (i >> 8) & 0xF;
                int z = (i >> 4) & 0xF;
                int y = i & 0xF;
                var rid = states[i].getRuntimeId();
                if (x != 0 && z != 0 && y != 0 && x != 15 && z != 15 && y != 15) {
                    var tmp = realOreToFakeMap.getOrDefault(rid, Integer.MAX_VALUE);
                    if (tmp != Integer.MAX_VALUE && canBeObfuscated(transparentBlockSet, x, y, z)) {
                        rid = tmp;
                    } else {
                        var tmp2 = fakeBlockMap.get(rid);
                        if (tmp2 != null && (nukkitRandom.nextSignedInt() & XAndDenominator) == 0 && canBeObfuscated(transparentBlockSet, x, y, z)) {
                            rid = tmp2.getInt(nukkitRandom.nextRange(0, tmp2.size() - 1));
                        }
                    }
                }
                palette.setBlock(i, rid);
            }
            this.needReObfuscate = false;
            setFlag(FLAG_PALETTE_UPDATED, true);
        }
        palette.writeTo(stream);
    }

    @PowerNukkitXOnly
    @Since("1.19.21-r1")
    public void setNeedReObfuscate() {
        this.needReObfuscate = true;
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public void iterateStates(BlockPositionDataConsumer consumer) {
        for (int i = 0; i < states.length; i++) {
            // XZY = Bedrock format
            //int index = (x << 8) + (z << 4) + y; // XZY = Bedrock format
            int x = (i >> 8) & 0xF;
            int z = (i >> 4) & 0xF;
            int y = i & 0xF;
            consumer.accept(x, y, z, states[i]);
        }
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public int getBlockChangeStateAbove(int x, int y, int z) {
        BitSet denyFlags = this.denyStates;
        if (denyFlags == null) {
            return 0;
        }
        int index = getIndex(x, y, z) << 1;
        return (denyFlags.get(index) ? 0x1 : 0x0) | (denyFlags.get(index + 1) ? 0x2 : 0x0);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy