Flandre923
1943 words
10 minutes
13 给方块上了保护

13 给方块上了保护#


@EventBusSubscriber
public class BeforeBlockBreakHandler { // 如果方块上了保护了,就取消这次的破坏事件

    @SubscribeEvent
    public static void OnBlockBreak (BlockEvent.BreakEvent event){
        BlockPos pos = event.getPos();
        int level = BlockEnchantmentStorage.getLevel(Enchantments.PROTECTION, pos);
        if (level>0 && !event.getPlayer().isCreative()){
            event.setCanceled(true);
        }
    }
}

这里有一个问题就是,对于上了附魔的方块,放置在世界上之后我们并不知道这个方块是否是一个附魔了的方块,所以我们需要写一个方法,用于保存对应的拥有附魔的方块放置到了什么位置,记录这个位置和对应这个位置方块拥有的附魔.

/**
 * 此类提供存储和检索方块附魔信息的方法。
 */
public class BlockEnchantmentStorage {

    /**
     * 将附魔信息添加到指定的方块位置。
     * 
     * @param blockPos 要添加附魔信息的方块位置
     * @param enchantments 附魔信息列表
     */
    public static void addBlockEnchantment(BlockPos blockPos, ListTag enchantments) {
        MinecraftServer server = ServerManager.getServerInstance(); // 获取服务器实例
        // 创建 StateSaverAndLoader 实例
        BlockStateSaverAndLoader state = BlockStateSaverAndLoader.getServerState(server);
        // 将方块附魔信息添加到列表中
        state.blockEnchantments.add(new BlockStateSaverAndLoader.BlockEnchantInfo(blockPos, enchantments));
    }

    /**
     * 移除指定方块位置的附魔信息。
     * 
     * @param blockPos 要移除附魔信息的方块位置
     */
    public static void removeBlockEnchantment(BlockPos blockPos) {
        MinecraftServer server = ServerManager.getServerInstance(); // 获取服务器实例
        // 获取 BlockStateSaverAndLoader 实例
        BlockStateSaverAndLoader state = BlockStateSaverAndLoader.getServerState(server);
        // 调用 StateSaverAndLoader 类中的方法来移除指定位置的方块附魔信息
        state.removeBlockEnchantment(blockPos);
    }

    /**
     * 获取指定方块位置的附魔信息。
     * 
     * @param blockPos 要获取附魔信息的方块位置
     * @return 返回方块位置的附魔信息列表,如果没有找到则返回空列表
     */
    public static ListTag getEnchantmentsAtPosition(BlockPos blockPos) {
        MinecraftServer server = ServerManager.getServerInstance(); // 获取服务器实例
        // 获取 BlockStateSaverAndLoader 实例
        BlockStateSaverAndLoader state = BlockStateSaverAndLoader.getServerState(server);

        // 遍历附魔信息列表,找到指定位置的方块附魔信息
        for (BlockStateSaverAndLoader.BlockEnchantInfo blockEnchantment : state.blockEnchantments) {
            if (blockEnchantment.blockPos.equals(blockPos)) {
                // 返回指定位置的方块附魔名称
                return blockEnchantment.enchantments;
            }
        }
        // 如果没有找到指定位置的方块附魔信息,则返回空列表
        return new ListTag();
    }

    /**
     * 获取指定方块位置的特定附魔的等级。
     * 
     * @param enchantment 要查询的附魔类型
     * @param blockPos 要查询附魔的方块位置
     * @return 返回附魔等级,如果没有找到则返回0
     */
    public static int getLevel(ResourceKey<Enchantment> enchantment, BlockPos blockPos) {
        MinecraftServer server = ServerManager.getServerInstance(); // 获取服务器实例
        // 获取方块的附魔信息
        ListTag enchantments = getEnchantmentsAtPosition(blockPos);

        // 遍历附魔信息
        for (int i = 0; i < enchantments.size(); i++) {
            // 获取单个附魔信息
            CompoundTag enchantmentInfo = enchantments.getCompound(i);

            // 提取附魔名称和等级
            String enchantmentName = enchantmentInfo.getString("id");
            int level = enchantmentInfo.getInt("lvl");

            // 检查附魔名称是否匹配
            if (enchantmentName.equals(String.valueOf(enchantment))) {
                // 返回附魔等级
                return level;
            }
        }

        // 如果没有找到匹配的附魔信息,默认返回0
        return 0;
    }
}

在上述的代码中我们使用到了ServerManager​这个类,这个类是我们自己写的,主要用于世界初始化的时候获得对应的MinecraftServer​对象.


@EventBusSubscriber
public class ServerManager
{
    public static MinecraftServer serverInstance;

    public static void setServerInstance(MinecraftServer server){
        serverInstance = server;
    }

    public static MinecraftServer getServerInstance(){
        return serverInstance;
    }

    @SubscribeEvent
    public static void onServerStarted(ServerStartedEvent event){
        serverInstance = event.getServer();
    }
}

这里的存储和读取都是依赖于BlockStateSaverAndLoader​,这个类是保存了对应的list的类,并且负责序列化和反序列化当前的list.即保存和读取,使用了SavedData​这个技术,将对应的nbt数据存储到了对应的维度下.这个存储是维度级别的,即这个数据是在维度共享的.你可以通过这个实现一个跨纬度传送的一些内容.(可以见boson的levelsavedata章节,1.20.4版本的代码可以看我教程)

/**
 * 此类用于存储和加载方块的附魔信息。
 */
public class BlockStateSaverAndLoader extends SavedData {
    /**
     * 存储所有方块的附魔信息的列表。
     */
    public static List<BlockEnchantInfo> blockEnchantments = new CopyOnWriteArrayList<>();

    /**
     * 用于存储单个方块的附魔信息。
     */
    public static class BlockEnchantInfo {
        public BlockPos blockPos; // 附魔方块的位置
        public ListTag enchantments; // 附魔的NBT数据

        /**
         * 构造函数,初始化方块位置和附魔数据。
         * @param blockPos 附魔方块的位置
         * @param enchantments 附魔的NBT数据
         */
        public BlockEnchantInfo(BlockPos blockPos, ListTag enchantments) {
            this.blockPos = blockPos;
            this.enchantments = enchantments;
        }

        /**
         * 重写equals方法,用于比较两个BlockEnchantInfo对象是否相等。
         * @param o 要比较的对象
         * @return 如果对象相同或包含相同的blockPos和enchantments则返回true,否则返回false
         */
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            BlockEnchantInfo that = (BlockEnchantInfo) o;
            return Objects.equals(blockPos, that.blockPos) &&
                    Objects.equals(enchantments, that.enchantments);
        }

        /**
         * 重写hashCode方法,用于计算对象的哈希码。
         * @return 基于blockPos和enchantments的哈希码
         */
        @Override
        public int hashCode() {
            return Objects.hash(blockPos, enchantments);
        }
    }

    /**
     * 覆盖save方法,用于将方块附魔信息保存到NBT标签中。
     * @param tag 要保存的NBT标签
     * @param registries 注册表提供者
     * @return 包含方块附魔信息的NBT标签
     */
    @Override
    public CompoundTag save(CompoundTag tag, HolderLookup.Provider registries) {
        ListTag blockEnchantmentsList = new ListTag(); // 创建NBT列表
        for (BlockEnchantInfo blockEnchantment : blockEnchantments) {
            CompoundTag blockEnchantmentNbt = new CompoundTag(); // 创建NBT标签
            blockEnchantmentNbt.putIntArray("BlockPos", new int[]{blockEnchantment.blockPos.getX(), blockEnchantment.blockPos.getY(), blockEnchantment.blockPos.getZ()}); // 将方块位置存储为整数数组
            blockEnchantmentNbt.put("Enchantments", blockEnchantment.enchantments); // 将附魔信息存储到NBT标签
            blockEnchantmentsList.add(blockEnchantmentNbt); // 将NBT标签添加到列表
        }
        tag.put("BlockEnchantments", blockEnchantmentsList); // 将列表存储到NBT标签
        return tag; // 返回包含方块附魔信息的NBT标签
    }

    /**
     * 从NBT标签创建BlockStateSaverAndLoader对象。
     * @param nbt 包含方块附魔信息的NBT标签
     * @param lookup 注册表提供者
     * @return 创建的BlockStateSaverAndLoader对象
     */
    public static BlockStateSaverAndLoader createFromNbt(CompoundTag nbt, HolderLookup.Provider lookup) {
        BlockStateSaverAndLoader state = new BlockStateSaverAndLoader(); // 创建对象

        // 读取方块附魔信息
        ListTag blockEnchantmentsList = nbt.getList("BlockEnchantments", 10); // 获取NBT列表
        for (int i = 0; i < blockEnchantmentsList.size(); i++) {
            CompoundTag blockEnchantmentNbt = blockEnchantmentsList.getCompound(i); // 获取NBT标签
            int[] posArray = blockEnchantmentNbt.getIntArray("BlockPos"); // 获取方块位置
            BlockPos blockPos = new BlockPos(posArray[0], posArray[1], posArray[2]);
            ListTag enchantments = blockEnchantmentNbt.getList("Enchantments", 10); // 获取附魔信息
            // 将读取的方块附魔信息添加到列表中
            state.blockEnchantments.add(new BlockEnchantInfo(blockPos, enchantments));
        }

        return state; // 返回创建的对象
    }

    /**
     * 定义创建BlockStateSaverAndLoader对象的工厂。
     */
    private static Factory<BlockStateSaverAndLoader> type = new Factory<>(
            BlockStateSaverAndLoader::new, // 若不存在 'BlockStateSaverAndLoader' 则创建
            BlockStateSaverAndLoader::createFromNbt, // 若存在 'BlockStateSaverAndLoader' NBT, 则调用 'createFromNbt' 传入参数
            null // 此处理论上应为 'DataFixTypes' 的枚举,但我们直接传递为空(null)也可以
    );

    /**
     * 从Minecraft服务器获取BlockStateSaverAndLoader状态。
     * @param server Minecraft服务器实例
     * @return BlockStateSaverAndLoader状态对象
     */
    public static BlockStateSaverAndLoader getServerState(MinecraftServer server) {
        if(server!=null) {
            DimensionDataStorage persistentStateManager = server.getLevel(Level.OVERWORLD).getDataStorage(); // 获取持久状态管理器

            // 当第一次调用了方法 'getOrCreate' 后,它会创建新的 'BlockStateSaverAndLoader' 并将其存储于 'PersistentStateManager' 中。
            // 'getOrCreate' 的后续调用将本地的 'BlockStateSaverAndLoader' NBT 传递给 'BlockStateSaverAndLoader::createFromNbt'。
            BlockStateSaverAndLoader state = persistentStateManager.computeIfAbsent(type, NeoMafishMod.MODID + "_block_enchantments"); // 获取或创建状态对象

            // 若状态未标记为脏(dirty),当 Minecraft 关闭时, 'writeNbt' 不会被调用,相应地,没有数据会被保存。
            // 从技术上讲,只有在事实上发生数据变更时才应当将状态标记为脏(dirty)。
            // 但大多数开发者和模组作者会对他们的数据未能保存而感到困惑,所以不妨直接使用 'markDirty' 。
            // 另外,这只将对应的布尔值设定为 TRUE,代价是文件写入磁盘时模组的状态不会有任何改变。(这种情况非常少见)
            state.setDirty(); // 标记状态为脏,确保数据被保存
            return state; // 返回状态对象
        }
        return null; // 如果服务器为空,则返回null
    }

    /**
     * 移除指定位置的方块附魔信息。
     * @param targetBlockPos 要移除附魔的方块位置
     */
    public void removeBlockEnchantment(BlockPos targetBlockPos) {
        // 使用removeIf方法移除列表中符合条件的元素
        blockEnchantments.removeIf(blockEnchantment -> blockEnchantment.blockPos.equals(targetBlockPos));
    }
}

至于如何将方块添加到list的我只需要,使用放置方块的事件或者mixin方块放置的方法即可.之后会将讲解代码.

13 给方块上了保护
https://fuwari.vercel.app/posts/minecraft1_21_0/13_给方块上了保护/
Author
Flandre923
Published at
2024-08-24