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

cn.nukkit.blockstate.BlockStateRegistry Maven / Gradle / Ivy

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

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.BlockID;
import cn.nukkit.block.BlockUnknown;
import cn.nukkit.block.customblock.CustomBlock;
import cn.nukkit.blockproperty.*;
import cn.nukkit.blockproperty.exception.BlockPropertyNotFoundException;
import cn.nukkit.blockstate.exception.InvalidBlockStateException;
import cn.nukkit.nbt.NBTIO;
import cn.nukkit.nbt.tag.CompoundTag;
import cn.nukkit.nbt.tag.LinkedCompoundTag;
import cn.nukkit.nbt.tag.Tag;
import cn.nukkit.utils.BinaryStream;
import cn.nukkit.utils.HumanStringComparator;
import cn.nukkit.utils.MinecraftNamespaceComparator;
import com.google.common.base.Preconditions;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.UtilityClass;
import lombok.extern.log4j.Log4j2;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.io.*;
import java.nio.ByteOrder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@PowerNukkitOnly
@Since("1.4.0.0-PN")
@UtilityClass
@ParametersAreNonnullByDefault
@Log4j2
public class BlockStateRegistry {
    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public final int BIG_META_MASK = 0xFFFFFFFF;
    private final ExecutorService asyncStateRemover = Executors.newSingleThreadExecutor(t -> new Thread(t, "BlockStateRegistry#asyncStateRemover"));
    private final Pattern BLOCK_ID_NAME_PATTERN = Pattern.compile("^blockid:(\\d+)$");

    private Registration updateBlockRegistration;

    private final Map blockStateRegistration = new ConcurrentHashMap<>();
    private final Map stateIdRegistration = new ConcurrentHashMap<>();
    private final Int2ObjectMap runtimeIdRegistration = new Int2ObjectOpenHashMap<>();

    private final Int2ObjectMap blockIdToPersistenceName = new Int2ObjectOpenHashMap<>();
    private final Map persistenceNameToBlockId = new LinkedHashMap<>();

    private byte[] blockPaletteBytes;

    private List knownStateIds;

    //
    static {
        init();
    }
    //

    private void init() {
        //
        try (InputStream stream = Server.class.getModule().getResourceAsStream("block_ids.csv")) {
            if (stream == null) {
                throw new AssertionError("Unable to locate block_ids.csv");
            }

            int count = 0;
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    count++;
                    line = line.trim();
                    if (line.isEmpty()) {
                        continue;
                    }
                    String[] parts = line.split(",");
                    Preconditions.checkArgument(parts.length == 2 || parts[0].matches("^[0-9]+$"));
                    if (parts.length > 1 && parts[1].startsWith("minecraft:")) {
                        int id = Integer.parseInt(parts[0]);
                        blockIdToPersistenceName.put(id, parts[1]);
                        persistenceNameToBlockId.put(parts[1], id);
                    }
                }
            } catch (Exception e) {
                throw new IOException("Error reading the line " + count + " of the block_ids.csv", e);
            }

        } catch (IOException e) {
            throw new AssertionError(e);
        }
        //

        //
        List tags = new ArrayList<>();
        List loadingKnownStateIds = new ArrayList<>();
        try (InputStream stream = Server.class.getModule().getResourceAsStream("canonical_block_states.nbt")) {
            if (stream == null) {
                throw new AssertionError("Unable to locate block state nbt");
            }

            try (BufferedInputStream bis = new BufferedInputStream(stream)) {
                int runtimeId = 0;
                while (bis.available() > 0) {
                    CompoundTag tag = NBTIO.read(bis, ByteOrder.BIG_ENDIAN, true);
                    tag.putInt("runtimeId", runtimeId++);
                    tag.putInt("blockId", persistenceNameToBlockId.getOrDefault(tag.getString("name").toLowerCase(Locale.ENGLISH), -1));
                    tags.add(tag);
                    loadingKnownStateIds.add(getStateId(tag));
                }
            }
            knownStateIds = loadingKnownStateIds;
        } catch (IOException e) {
            throw new AssertionError(e);
        }
        //
        Integer infoUpdateRuntimeId = null;

        Set warned = new HashSet<>();

        for (CompoundTag state : tags) {
            int blockId = state.getInt("blockId");
            int runtimeId = state.getInt("runtimeId");
            String name = state.getString("name").toLowerCase();
            if (name.equals("minecraft:unknown")) {
                infoUpdateRuntimeId = runtimeId;
            }

            // Special condition: minecraft:wood maps 3 blocks, minecraft:wood, minecraft:log and minecraft:log2
            // All other cases, register the name normally
            if (isNameOwnerOfId(name, blockId)) {
                registerPersistenceName(blockId, name);
                registerStateId(state, runtimeId);
            } else if (blockId == -1) {
                if (warned.add(name)) {
                    log.warn("Unknown block id for the block named {}", name);
                }
                registerStateId(state, runtimeId);
            }
        }

        /*for (var block : warned) {
            var id = 255-RuntimeItems.getRuntimeMapping().getNetworkIdByNamespaceId(block).getAsInt();
            try {
                var path = Path.of("test.txt");
                if (!path.toFile().exists()) path.toFile().createNewFile();
                Files.writeString(path, id + "," + block + "\n", StandardCharsets.UTF_8, StandardOpenOption.APPEND);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }*/

        if (infoUpdateRuntimeId == null) {
            throw new IllegalStateException("Could not find the minecraft:info_update runtime id!");
        }

        updateBlockRegistration = findRegistrationByRuntimeId(infoUpdateRuntimeId);

        try {
            blockPaletteBytes = NBTIO.write(tags, ByteOrder.LITTLE_ENDIAN, true);
        } catch (IOException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    private boolean isNameOwnerOfId(String name, int blockId) {
        return blockId != -1 && !name.equals("minecraft:wood") || blockId == BlockID.WOOD_BARK;
    }

    @Nonnull
    private String getStateId(CompoundTag block) {
        Map propertyMap = new TreeMap<>(HumanStringComparator.getInstance());
        for (Tag tag : block.getCompound("states").getAllTags()) {
            propertyMap.put(tag.getName(), tag.parseValue().toString());
        }

        String blockName = block.getString("name").toLowerCase(Locale.ENGLISH);
        Preconditions.checkArgument(!blockName.isEmpty(), "Couldn't find the block name!");
        StringBuilder stateId = new StringBuilder(blockName);
        propertyMap.forEach((name, value) -> stateId.append(';').append(name).append('=').append(value));
        return stateId.toString();
    }

    @Nullable
    private static Registration findRegistrationByRuntimeId(int runtimeId) {
        return runtimeIdRegistration.get(runtimeId);
    }

    @PowerNukkitOnly
    @Since("1.5.2.0-PN")
    @Nullable
    public String getKnownBlockStateIdByRuntimeId(int runtimeId) {
        if (runtimeId >= 0 && runtimeId < knownStateIds.size()) {
            return knownStateIds.get(runtimeId);
        }
        return null;
    }

    @PowerNukkitOnly
    @Since("1.5.2.0-PN")
    public int getKnownRuntimeIdByBlockStateId(String stateId) {
        int result = knownStateIds.indexOf(stateId);
        if (result != -1) {
            return result;
        }
        BlockState state;
        try {
            state = BlockState.of(stateId);
        } catch (NoSuchElementException | IllegalStateException | IllegalArgumentException ignored) {
            return -1;
        }
        String fullStateId = state.getStateId();
        return knownStateIds.indexOf(fullStateId);
    }

    /**
     * @return {@code null} if the runtime id does not matches any known block state.
     */
    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @Nullable
    public BlockState getBlockStateByRuntimeId(int runtimeId) {
        Registration registration = findRegistrationByRuntimeId(runtimeId);
        if (registration == null) {
            return null;
        }
        BlockState state = registration.state;
        if (state != null) {
            return state;
        }
        CompoundTag originalBlock = registration.originalBlock;
        if (originalBlock != null) {
            state = buildStateFromCompound(originalBlock);
            if (state != null) {
                registration.state = state;
                registration.originalBlock = null;
            }
        }
        return state;
    }

    @Nullable
    private BlockState buildStateFromCompound(CompoundTag block) {
        String name = block.getString("name").toLowerCase(Locale.ENGLISH);
        Integer id = getBlockId(name);
        if (id == null) {
            return null;
        }

        BlockState state = BlockState.of(id);

        CompoundTag properties = block.getCompound("states");
        for (Tag tag : properties.getAllTags()) {
            state = state.withProperty(tag.getName(), tag.parseValue().toString());
        }

        return state;
    }

    private static NoSuchElementException runtimeIdNotRegistered(int runtimeId) {
        return new NoSuchElementException("The block id for the runtime id " + runtimeId + " is not registered");
    }

    @PowerNukkitOnly
    @Since("1.5.2.0-PN")
    public int getBlockIdByRuntimeId(int runtimeId) {
        Registration registration = findRegistrationByRuntimeId(runtimeId);
        if (registration == null) {
            throw runtimeIdNotRegistered(runtimeId);
        }
        BlockState state = registration.state;
        if (state != null) {
            return state.getBlockId();
        }
        CompoundTag originalBlock = registration.originalBlock;
        if (originalBlock == null) {
            throw runtimeIdNotRegistered(runtimeId);
        }
        try {
            state = buildStateFromCompound(originalBlock);
        } catch (BlockPropertyNotFoundException e) {
            String name = originalBlock.getString("name").toLowerCase(Locale.ENGLISH);
            Integer id = getBlockId(name);
            if (id == null) {
                throw runtimeIdNotRegistered(runtimeId);
            }
            return id;
        }
        if (state != null) {
            registration.state = state;
            registration.originalBlock = null;
        } else {
            throw runtimeIdNotRegistered(runtimeId);
        }
        return state.getBlockId();
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public int getRuntimeId(BlockState state) {
        if (state.getBlockId() > Block.MAX_BLOCK_ID && !Block.ID_TO_CUSTOM_BLOCK.containsKey(state.getBlockId())) {
            return stateIdRegistration.get(blockIdToPersistenceName.get(state.getBlockId())).runtimeId;
        }
        return getRegistration(convertToNewState(state)).runtimeId;
    }

    private BlockState convertToNewState(BlockState oldState) {
        // Check OldWoodBarkUpdater.java and https://minecraft.fandom.com/wiki/Log#Metadata
        // The Only bark variant is replaced in the client side to minecraft:wood with the same wood type
        if (oldState.getBitSize() == 4 && (oldState.getBlockId() == BlockID.LOG || oldState.getBlockId() == BlockID.LOG2)) {
            int exactInt = oldState.getExactIntStorage();
            if ((exactInt & 0b1100) == 0b1100) {
                int increment = oldState.getBlockId() == BlockID.LOG ? 0b000 : 0b100;
                return BlockState.of(BlockID.WOOD_BARK, (exactInt & 0b11) + increment);
            }
        }
        return oldState;
    }

    private Registration getRegistration(BlockState state) {
        return blockStateRegistration.computeIfAbsent(state, BlockStateRegistry::findRegistration);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public int getRuntimeId(int blockId) {
        return getRuntimeId(BlockState.of(blockId));
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @Deprecated
    @DeprecationDetails(reason = "The meta is limited to 32 bits", replaceWith = "getRuntimeId(BlockState state)", since = "1.3.0.0-PN")
    public int getRuntimeId(int blockId, int meta) {
        return getRuntimeId(BlockState.of(blockId, meta));
    }

    private Registration findRegistration(final BlockState state) {
        // Special case for PN-96 PowerNukkit#210 where the world contains blocks like 0:13, 0:7, etc
        if (state.getBlockId() == BlockID.AIR) {
            Registration airRegistration = blockStateRegistration.get(BlockState.AIR);
            if (airRegistration != null) {
                return new Registration(state, airRegistration.runtimeId, null);
            }
        }

        Registration registration = findRegistrationByStateId(state);
        removeStateIdsAsync(registration);
        return registration;
    }

    private Registration findRegistrationByStateId(BlockState state) {
        Registration registration;
        try {
            registration = stateIdRegistration.remove(state.getStateId());
            if (registration != null) {
                registration.state = state;
                registration.originalBlock = null;
                return registration;
            }
        } catch (Exception e) {
            try {
                log.fatal("An error has occurred while trying to get the stateId of state: "
                                + "{}:{}"
                                + " - {}"
                                + " - {}",
                        state.getBlockId(),
                        state.getDataStorage(),
                        state.getProperties(),
                        blockIdToPersistenceName.get(state.getBlockId()),
                        e);
            } catch (Exception e2) {
                e.addSuppressed(e2);
                log.fatal("An error has occurred while trying to get the stateId of state: {}:{}",
                        state.getBlockId(), state.getDataStorage(), e);
            }
        }

        try {
            registration = stateIdRegistration.remove(state.getLegacyStateId());
            if (registration != null) {
                registration.state = state;
                registration.originalBlock = null;
                return registration;
            }
        } catch (Exception e) {
            log.fatal("An error has occurred while trying to parse the legacyStateId of {}:{}", state.getBlockId(), state.getDataStorage(), e);
        }

        return logDiscoveryError(state);
    }

    private void removeStateIdsAsync(@Nullable Registration registration) {
        if (registration != null && registration != updateBlockRegistration) {
            asyncStateRemover.submit(() -> stateIdRegistration.values().removeIf(r -> r.runtimeId == registration.runtimeId));
        }
    }

    private Registration logDiscoveryError(BlockState state) {
        log.error("Found an unknown BlockId:Meta combination: {}:{}"
                        + " - {}"
                        + " - {}"
                        + " - {}"
                        + ", trying to repair or replacing with an \"UPDATE!\" block.",
                state.getBlockId(), state.getDataStorage(), state.getStateId(), state.getProperties(),
                blockIdToPersistenceName.get(state.getBlockId())
        );
        return updateBlockRegistration;
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public List getPersistenceNames() {
        return new ArrayList<>(persistenceNameToBlockId.keySet());
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @Nonnull
    public String getPersistenceName(int blockId) {
        String persistenceName = blockIdToPersistenceName.get(blockId);
        if (persistenceName == null) {
            String fallback = "blockid:" + blockId;
            log.warn("The persistence name of the block id {} is unknown! Using {} as an alternative!", blockId, fallback);
            registerPersistenceName(blockId, fallback);
            return fallback;
        }
        return persistenceName;
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public void registerPersistenceName(int blockId, String persistenceName) {
        synchronized (blockIdToPersistenceName) {
            String newName = persistenceName.toLowerCase();
            String oldName = blockIdToPersistenceName.putIfAbsent(blockId, newName);
            if (oldName != null && !persistenceName.equalsIgnoreCase(oldName)) {
                throw new UnsupportedOperationException("The persistence name registration tried to replaced a name. Name:" + persistenceName + ", Old:" + oldName + ", Id:" + blockId);
            }
            Integer oldId = persistenceNameToBlockId.putIfAbsent(newName, blockId);
            if (oldId != null && blockId != oldId) {
                blockIdToPersistenceName.remove(blockId);
                throw new UnsupportedOperationException("The persistence name registration tried to replaced an id. Name:" + persistenceName + ", OldId:" + oldId + ", Id:" + blockId);
            }
        }
    }


    @PowerNukkitOnly
    @Since("1.6.0.0-PNX")
    public synchronized static void registerCustomBlockState(List blockCustoms) {
        //清空原本的数据
        blockStateRegistration.clear();
        stateIdRegistration.clear();
        runtimeIdRegistration.clear();
        //按照每组方块(因为每个方块可能有多种状态,将他们归为一个List)的namespace(形如minecraft:xxx)升序排序(遍历时Hash值小的在前面)
        SortedMap> namespace2Nbt = new TreeMap<>(getBlockIdComparator());
        //处理原版方块
        try (InputStream stream = Server.class.getModule().getResourceAsStream("canonical_block_states.nbt")) {
            if (stream == null) {
                throw new AssertionError("Unable to locate block state nbt");
            }
            try (BufferedInputStream bis = new BufferedInputStream(stream)) {
                while (bis.available() > 0) {
                    CompoundTag tag = NBTIO.read(bis, ByteOrder.BIG_ENDIAN, true);
                    var name = tag.getString("name");
                    tag.putInt("blockId", persistenceNameToBlockId.getOrDefault(tag.getString("name").toLowerCase(), -1));
                    if (!namespace2Nbt.containsKey(name)) {
                        //每个方块可能有多种状态,将他们归为一个ArrayList
                        namespace2Nbt.put(name, new ArrayList<>());
                    }
                    namespace2Nbt.get(name).add(tag);
                }
            }
        } catch (IOException e) {
            throw new AssertionError(e);
        }

        var version = -1;
        //处理自定义方块
        for (var blockCustom : blockCustoms) {
            var namespace = blockCustom.getNamespaceId();
            blockIdToPersistenceName.put(blockCustom.getId(), namespace);
            persistenceNameToBlockId.put(namespace, blockCustom.getId());
            if (!knownStateIds.contains(namespace)) knownStateIds.add(namespace);
            CompoundTag nbt = new CompoundTag()
                    .putInt("blockId", blockCustom.getId())
                    .putString("name", namespace)
                    .putInt("version", version == -1 ? version = namespace2Nbt.values().stream().findFirst().get().get(0).getInt("version") : version)
                    .putCompound("states", new CompoundTag("states"));
            var nbtList = new ArrayList();

            // 多状态方块注册
            if (blockCustom instanceof Block block) {
                var properties = block.getProperties().getAllProperties()
                        .stream().map(BlockProperties.RegisteredBlockProperty::getProperty).toList();
                List stateNbtList = null;
                for (var eachProperty : properties) {
                    var newStateNbtList = new LinkedList();
                    if (stateNbtList == null) {
                        if (eachProperty instanceof BooleanBlockProperty) {
                            newStateNbtList.add(new LinkedCompoundTag("states").putBoolean(eachProperty.getName(), false));
                            newStateNbtList.add(new LinkedCompoundTag("states").putBoolean(eachProperty.getName(), true));
                        } else if (eachProperty instanceof IntBlockProperty intBlockProperty) {
                            for (int i = intBlockProperty.getMinValue(); i <= intBlockProperty.getMaxValue(); i++) {
                                newStateNbtList.add(new LinkedCompoundTag("states").putInt(eachProperty.getName(), i));
                            }
                        } else if (eachProperty instanceof UnsignedIntBlockProperty unsignedIntBlockProperty) {
                            for (long i = unsignedIntBlockProperty.getMinValue(); i <= unsignedIntBlockProperty.getMaxValue(); i++) {
                                newStateNbtList.add(new LinkedCompoundTag("states").putLong(eachProperty.getName(), i));
                            }
                        } else if (eachProperty instanceof ArrayBlockProperty arrayBlockProperty) {
                            if (arrayBlockProperty.isOrdinal()) {
                                var universe = arrayBlockProperty.getUniverse();
                                for (int i = 0, universeLength = universe.length; i < universeLength; i++) {
                                    newStateNbtList.add(new LinkedCompoundTag("states").putInt(eachProperty.getName(), i));
                                }
                            } else {
                                for (var each : arrayBlockProperty.getUniverse()) {
                                    newStateNbtList.add(new LinkedCompoundTag("states").putString(eachProperty.getName(), each.toString()));
                                }
                            }
                        }
                    } else {
                        for (var stateNbt : stateNbtList) {
                            if (eachProperty instanceof BooleanBlockProperty) {
                                newStateNbtList.add(stateNbt.copy().putBoolean(eachProperty.getName(), false));
                                newStateNbtList.add(stateNbt.copy().putBoolean(eachProperty.getName(), true));
                            } else if (eachProperty instanceof IntBlockProperty intBlockProperty) {
                                for (int i = intBlockProperty.getMinValue(); i <= intBlockProperty.getMaxValue(); i++) {
                                    newStateNbtList.add(stateNbt.copy().putInt(eachProperty.getName(), i));
                                }
                            } else if (eachProperty instanceof UnsignedIntBlockProperty unsignedIntBlockProperty) {
                                for (long i = unsignedIntBlockProperty.getMinValue(); i <= unsignedIntBlockProperty.getMaxValue(); i++) {
                                    newStateNbtList.add(stateNbt.copy().putLong(eachProperty.getName(), i));
                                }
                            } else if (eachProperty instanceof ArrayBlockProperty arrayBlockProperty) {
                                if (arrayBlockProperty.isOrdinal()) {
                                    var universe = arrayBlockProperty.getUniverse();
                                    for (int i = 0, universeLength = universe.length; i < universeLength; i++) {
                                        newStateNbtList.add(stateNbt.copy().putInt(eachProperty.getName(), i));
                                    }
                                } else {
                                    for (var each : arrayBlockProperty.getUniverse()) {
                                        newStateNbtList.add(stateNbt.copy().putString(eachProperty.getName(), each.toString()));
                                    }
                                }
                            }
                        }
                    }
                    stateNbtList = newStateNbtList;
                }
                if (stateNbtList != null) {
                    for (var each : stateNbtList) {
                        nbtList.add(nbt.copy().putCompound("states", each));
                    }
                } else {
                    nbtList.add(nbt.clone());
                }
                namespace2Nbt.put(blockCustom.getNamespaceId(), nbtList);
            } else {
                nbtList.add(nbt.clone());
                namespace2Nbt.put(blockCustom.getNamespaceId(), nbtList);
            }
        }
        List tags = new ArrayList<>();
        Set warned = new HashSet<>();
        Integer infoUpdateRuntimeId = null;
        //由排序好的序列计算runtimeId(递增)
        int runtimeId = 0;
        for (var namespace : namespace2Nbt.keySet()) {
            for (var nbt : namespace2Nbt.get(namespace)) {
                nbt.putInt("runtimeId", runtimeId);
                tags.add(nbt);
                int block = nbt.getInt("blockId");
                String name = nbt.getString("name").toLowerCase();
                if (name.equals("minecraft:unknown")) {
                    infoUpdateRuntimeId = runtimeId;
                }
                if (isNameOwnerOfId(name, block)) {
                    //注册stateIdRegistration和runtimeIdRegistration
                    registerStateId(nbt, runtimeId);
                } else if (block == -1) {
                    if (warned.add(name)) {
                        log.warn("Unknown block id for the block named {}", name);
                    }
                    registerStateId(nbt, runtimeId);
                }
                ++runtimeId;
            }
        }
        if (infoUpdateRuntimeId == null) {
            throw new IllegalStateException("Could not find the minecraft:info_update runtime id!");
        }

        updateBlockRegistration = findRegistrationByRuntimeId(infoUpdateRuntimeId);

        try {
            blockPaletteBytes = NBTIO.write(tags, ByteOrder.LITTLE_ENDIAN, true);
        } catch (IOException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    @PowerNukkitXOnly
    @Since("1.6.0.0-PNX")
    public synchronized static void deleteCustomBlockState() {
        blockStateRegistration.clear();
        stateIdRegistration.clear();
        runtimeIdRegistration.clear();
        persistenceNameToBlockId.clear();
        blockIdToPersistenceName.clear();
        knownStateIds.clear();
        init();
    }

    private void registerStateId(CompoundTag block, int runtimeId) {
        String stateId = getStateId(block);
        Registration registration = new Registration(null, runtimeId, block);

        Registration old = stateIdRegistration.putIfAbsent(stateId, registration);
        if (old != null && !old.equals(registration)) {
            throw new UnsupportedOperationException("The persistence NBT registration tried to replaced a runtime id. Old:" + old + ", New:" + runtimeId + ", State:" + stateId);
        }

        runtimeIdRegistration.put(runtimeId, registration);
    }

    private void registerState(int blockId, int meta, CompoundTag originalState, int runtimeId) {
        BlockState state = BlockState.of(blockId, meta);
        Registration registration = new Registration(state, runtimeId, null);

        Registration old = blockStateRegistration.putIfAbsent(state, registration);
        if (old != null && !registration.equals(old)) {
            throw new UnsupportedOperationException("The persistence NBT registration tried to replaced a runtime id. Old:" + old + ", New:" + runtimeId + ", State:" + state);
        }
        runtimeIdRegistration.put(runtimeId, registration);

        stateIdRegistration.remove(getStateId(originalState));
        stateIdRegistration.remove(state.getLegacyStateId());
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public int getBlockPaletteDataVersion() {
        @SuppressWarnings("UnnecessaryLocalVariable")
        Object obj = blockPaletteBytes;
        return obj.hashCode();
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @Nonnull
    public byte[] getBlockPaletteBytes() {
        return blockPaletteBytes.clone();
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public void putBlockPaletteBytes(BinaryStream stream) {
        stream.put(blockPaletteBytes);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public int getBlockPaletteLength() {
        return blockPaletteBytes.length;
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public void copyBlockPaletteBytes(byte[] target, int targetIndex) {
        System.arraycopy(blockPaletteBytes, 0, target, targetIndex, blockPaletteBytes.length);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @SuppressWarnings({"deprecation", "squid:CallToDepreca"})
    @Nonnull
    public BlockProperties getProperties(int blockId) {
        int fullId = blockId << Block.DATA_BITS;
        if (blockId > Block.MAX_BLOCK_ID && Block.ID_TO_CUSTOM_BLOCK.get(blockId) instanceof Block block1) {
            return block1.getProperties();
        }
        Block block;
        if (fullId >= Block.fullList.length || fullId < 0 || (block = Block.fullList[fullId]) == null) {
            return BlockUnknown.PROPERTIES;
        }
        return block.getProperties();
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @Nonnull
    public MutableBlockState createMutableState(int blockId) {
        return getProperties(blockId).createMutableState(blockId);
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @Nonnull
    public MutableBlockState createMutableState(int blockId, int bigMeta) {
        MutableBlockState blockState = createMutableState(blockId);
        blockState.setDataStorageFromInt(bigMeta);
        return blockState;
    }

    /**
     * @throws InvalidBlockStateException
     */
    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @Nonnull
    public MutableBlockState createMutableState(int blockId, Number storage) {
        MutableBlockState blockState = createMutableState(blockId);
        blockState.setDataStorage(storage);
        return blockState;
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public int getUpdateBlockRegistration() {
        return updateBlockRegistration.runtimeId;
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    @Nullable
    public Integer getBlockId(String persistenceName) {
        Integer blockId = persistenceNameToBlockId.get(persistenceName);
        if (blockId != null) {
            return blockId;
        }

        Matcher matcher = BLOCK_ID_NAME_PATTERN.matcher(persistenceName);
        if (matcher.matches()) {
            try {
                return Integer.parseInt(matcher.group(1));
            } catch (NumberFormatException ignored) {
            }
        }
        return null;
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public int getFallbackRuntimeId() {
        return updateBlockRegistration.runtimeId;
    }

    @PowerNukkitOnly
    @Since("1.4.0.0-PN")
    public BlockState getFallbackBlockState() {
        return updateBlockRegistration.state;
    }

    @PowerNukkitXOnly
    @Since("1.19.50-r1")
    public static void close() {
        asyncStateRemover.shutdownNow();
    }

    private Comparator getBlockIdComparator() {
        return MinecraftNamespaceComparator::compareFNV;
    }

    @AllArgsConstructor
    @ToString
    @EqualsAndHashCode
    private static class Registration {
        @Nullable
        private BlockState state;

        private final int runtimeId;

        @Nullable
        private CompoundTag originalBlock;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy