Skip to content

机械动力 Fabric 蓝图加农炮NBT刷物品解决日志

作者 / AUTHOR 白墨麒麟 BaimoQilin

授权协议 / LICENSE CC-BY-NC-SA 署名-非商业性使用-相同方式共享

撰稿日期 / DATE OF WRITING 1/20/2025 16:26

最近,指星公益服1 腐竹Sophie_zhxi跟我说,适用于Fabric的新的刷物品Bug又出来了。在几经曲折后,我终于找到了Bug的源头并予以修复。以下是我的探究过程:

Step.1 尝试清除safe_nbt.json中的其他项

这次与上次使用“剪贴板”刷物品的方法 2 几乎相同,但所依托的载体是“高爆弹”。此前的解决方案是将data/create/tags/blocks/safe_nbt.json中的create:clipboard项删除,因此我首先想到可能又是safe_nbt.json的问题。这是safe_nbt.json的原内容:

JSON
{
  "replace": false,
  "values": [
    "create:creative_motor",
    "create:creative_fluid_tank",
    "create:mechanical_piston",
    "create:sticky_mechanical_piston",
    "create:windmill_bearing",
    "create:mechanical_bearing",
    "create:clockwork_bearing",
    "create:rope_pulley",
    "create:cart_assembler",
    "create:linear_chassis",
    "create:secondary_linear_chassis",
    "create:radial_chassis",
    "create:sequenced_gearshift",
    "create:rotation_speed_controller",
    "create:andesite_funnel",
    "create:andesite_belt_funnel",
    "create:brass_funnel",
    "create:brass_belt_funnel",
    "create:redstone_link",
    "create:analog_lever",
    "create:pulse_repeater",
    "create:pulse_extender",
    //"create:clipboard",
    "#minecraft:banners",
    "#minecraft:all_signs"
  ]
}

如果将其清空,则加农炮打印时理应不保留任何方块的NBT数据。

JSON
{
  "replace": false,
  "values": []
}

然而实际测试后,问题并没有的到解决。

复查:哪里出了问题: “高爆弹”并不是机械动力或原版的方块,本来就不存在与safe_nbt.json中。应该尝试其他的方案解决问题。

Step.2 为什么删除safe_nbt.json中的clipboard项有效?

根据之前的理解,safe_nbt.json是加农炮打印时保留NBT的“白名单”。

果真如此吗?

打开Create Fabric在GitHub托管的代码,借助Copilot的协助,我找到了负责加农炮蓝图打印的代码文件SchematicannonBlockEntity.java。第772行的launchBlockOrBelt负责处理每个方块的发射逻辑。797引起了我的注意:

Java
CompoundTag data = BlockHelper.prepareBlockEntityData(blockState, blockEntity);

这里调用了BlockHelper.prepareBlockEntityData,似乎与NBT过滤逻辑有关!顺藤摸瓜,在BlockHelper.java:238我找到了prepareBlockEntityData方法的定义:

Java
public static CompoundTag prepareBlockEntityData(BlockState blockState, BlockEntity blockEntity) {
    CompoundTag data = null;
    if (blockEntity == null)
        return data;

    if (AllBlockTags.SAFE_NBT.matches(blockState)) {
        data = blockEntity.saveWithFullMetadata();

    } else if (blockEntity instanceof IPartialSafeNBT) {
        data = new CompoundTag();
        ((IPartialSafeNBT) blockEntity).writeSafe(data);

    } else if (Mods.FRAMEDBLOCKS.contains(blockState.getBlock()))
        data = FramedBlocksInSchematics.prepareBlockEntityData(blockState, blockEntity);

    return NBTProcessors.process(blockState, blockEntity, data, true);
}

在这里,我们看到了熟悉的SAFE_NBT

由此可知,如果打印的方块存在于SAFE_NBT中,将会保存完整的NBT信息。这就解释了为什么删除safe_nbt.json中的clipboard项有效的问题。

Step.3 为什么“高爆弹”特殊?

为什么“高爆弹”本来就不在safe_nbt.json中,依然被保留了完整的NBT数据?

重读prepareBlockEntityData方法的定义,当高爆弹作为blockEntity传入时,会发生这些事情:

  1. 判断blockEntity是否为null:不是;

  2. 判断该方块是否在SAFE_NBT中:不在;

  3. 判断该方块是否有writeSafe不在

  4. 判断该方块是否在FRAMEDBLOCKS中:不在;

我们会发现:这里的else if条件居然全部不符合!

说明prepareBlockEntityData方法缺失了末尾else的兜底逻辑,导致返回的data值为null

只需在末尾添加兜底else即可3

Java
public static CompoundTag prepareBlockEntityData(BlockState blockState, BlockEntity blockEntity) {
    CompoundTag data = null;
    if (blockEntity == null)
        return data;

    if (AllBlockTags.SAFE_NBT.matches(blockState)) {
        data = blockEntity.saveWithFullMetadata();

    } else if (blockEntity instanceof IPartialSafeNBT) {
        data = new CompoundTag();
        ((IPartialSafeNBT) blockEntity).writeSafe(data);

    } else if (Mods.FRAMEDBLOCKS.contains(blockState.getBlock())) {
        data = FramedBlocksInSchematics.prepareBlockEntityData(blockState, blockEntity);
    } else {
        // 舍弃所有NBT
        data = blockEntity.saveWithoutMetadata();
    }


    return NBTProcessors.process(blockState, blockEntity, data, true);

Step.4 重理思路

令我万万没想到的是,看似天衣无缝的修复并没有奏效……

那问题究竟出在哪里呢?让我们重新梳理思路4

aaa7b13e1dfc0da356d7e4658b0aa9e5.png

究竟那一步出错了呢?

Step.5 外置safe_nbt和不靠谱的附属作者

prepareBlockEntityData方法的定义中,如果3个if条件均不符合,其实返回的data就是null,即不会保留任何NBT。这意味着,“高爆弹”被保留了NBT可反推得其一定满足3个条件中的一个5

果不其然,在“高爆弹”所属模组 机械动力: 火炮 代码的fabric/src/generated/resources/data/create/tags/blocks/safe_nbt.json:100,我找到了he_shell(即“高爆弹”)的身影。也就是说,模组将会将包括“高爆弹”在内的这些项目追加到safe_nbt白名单中。

这波真不能怪西米布比(

千算万算,没算到这不靠谱的附属作者喵~

Step.6 创飞全部NBT

不靠谱的附属作者可不止一个呢。为了一劳永逸地解决所有类似问题,我的临时解决方案是:在打印时直接舍弃所有NBT标签。当然,最终的解决方案是等所有这些附属作者都意识到这些问题并一一予以修复6

让我们开始吧qwq~

SchematicannonBlockEntity.java:797-798在调用prepareBlockEntityData后,将数据传给launchBlock。而位于同文件第808行的launchBlock方法定义如下:

Java
protected void launchBlock(BlockPos target, ItemStack stack, BlockState state, @Nullable CompoundTag data) {
    if (!state.isAir())    // 若该方块不是空气
        blocksPlaced++;    // 将已放置方块数量计数+1
    // 划重点!!这里是实际的放置逻辑
    flyingBlocks.add(new LaunchedItem.ForBlockState(this.getBlockPos(), target, stack, state, data));
    // 播放音效
    playFiringSound();
}

找到LaunchedItem.ForBlockState的定义文件LaunchedItem.javaForBlockState7,修改readNBTplace,使其在打印时不附带任何NBT:

Java
public static class ForBlockState extends LaunchedItem {
    public BlockState state;
    public CompoundTag data;

    ForBlockState() {}

    public ForBlockState(BlockPos start, BlockPos target, ItemStack stack, BlockState state, CompoundTag data) {
        super(start, target, stack);
        this.state = state;
        this.data = data;
    }

    @Override
    public CompoundTag serializeNBT() {
        CompoundTag serializeNBT = super.serializeNBT();
        serializeNBT.put("BlockState", NbtUtils.writeBlockState(state));
        if (data != null) {
            data.remove("x");
            data.remove("y");
            data.remove("z");
            data.remove("id");
            serializeNBT.put("Data", data);
        }
        return serializeNBT;
    }

    @Override
    void readNBT(CompoundTag nbt, HolderGetter<Block> holderGetter) {
        super.readNBT(nbt, holderGetter);
        state = NbtUtils.readBlockState(holderGetter, nbt.getCompound("BlockState"));
        // Don't preserve any custom data
        data = new CompoundTag();
    }

    @Override
    void place(Level world) {
        // Pass an empty compound to discard NBT
        BlockHelper.placeSchematicBlock(world, state, target, stack, new CompoundTag());
    }

}

Step.7 最终测试

将代码编译8,测试后发现确实无法打印出NBT了(!!!!)

d1e811e7bc1ed1c66fa3025df4a7dfd8.png

结语

白墨其实根本没正经地系统性学过Java,有很多地方依靠GitHub Copilot的帮助~

然后也欢迎各位来我的小服玩哦!群号: 640159882

最终编译出来的文件,也在群文件里喵 但是还是建议各位腐竹自行修改代码文件编译,熟悉以下机械动力代码喵。


  1. 又名AimToStar,群号77612911,主打Fabric整合包轮换的公益服,创立于2022年11月末。 

  2. 另见http://www.bilibili.com/video/BV1GNUrYaEhB。 

  3. 这里的saveWithoutMetadata方法是在Minecraft原版的net.minecraft.world.level.block.entityBlockEntitiy.class中定义的,需要反编译源代码获得。 

  4. 截图中else内的data=...与前文不符,以前文为准。 

  5. 对于其他其他两个条件,writeSafe指的是“仅保留安全的NBT”,framedblock指的是“方块上的物品”(需要将物品上的物品也放入打印材料中)。 

  6. 碰到其他附属的相关问题,如果有时间可以随手向作者提个issue哦。机械动力:火炮的issue我已经提过了:https://github.com/Cannoneers-of-Create/CreateBigCannons/issues/746 

  7. 位于该文件第96行。 

  8. 注意:需将build.gradle:16行的System.getenv("GITHUB_RUN_NUMBER")改为1631,否则会导致编译出来的版本不对,被依赖识别错误。