原料

原料

Ingredient 用于在配方中检查给定 ItemStack 是否为有效输入。为此,Ingredient 实现了 Predicate<ItemStack>,可以调用 #test 确认给定堆叠是否匹配原料。

不过,Ingredient 的许多内部实现较为混乱。NeoForge 尽可能绕过 Ingredient 类,引入 ICustomIngredient 接口处理自定义原料。它不是普通 Ingredient 的直接替代品,但可分别通过 ICustomIngredient#toVanilla 与 Ingredient#getCustomIngredient 在两者之间转换。

内置原料类型

获取原料最简单的方法是使用 Ingredient#of 辅助方法,它有多种变体:

  • Ingredient.of() 返回空原料。
  • Ingredient.of(Blocks.IRON_BLOCK, Items.GOLD_BLOCK) 返回接受铁块或金块的原料。参数是 ItemLike 的可变参数,因此可以使用任意数量的方块和物品。
  • Ingredient.of(Stream.of(Items.DIAMOND_SWORD)) 返回接受指定物品的原料。与前一种方法类似,不过接收 Stream<ItemLike>,适合恰好已有此类流的情况。
  • Ingredient.of(BuiltInRegistries.ITEM.getOrThrow(ItemTags.WOODEN_SLABS)) 返回接受指定标签中任意物品的原料,例如任意木台阶。

NeoForge 还添加了一些额外原料:

  • new BlockTagIngredient(BlockTags.CONVERTABLE_TO_MUD) 返回类似 Ingredient.of() 标签变体的原料,但使用方块标签。适用于本来想使用物品标签、却只有方块标签可用的情况,例如 minecraft:convertable_to_mud。
  • CustomDisplayIngredient.of(Ingredient.of(Items.DIRT), SlotDisplay.Empty.INSTANCE) 返回带有自定义 SlotDisplay 的原料,由你提供该显示对象,决定客户端渲染时如何处理此槽位。
  • CompoundIngredient.of(Ingredient.of(Items.DIRT)) 返回包含子原料的原料,子原料通过构造函数的可变参数传入。任一子原料匹配时,它就匹配。
  • DataComponentIngredient.of(true, new ItemStack(Items.DIAMOND_SWORD)) 返回除了物品本身还匹配数据组件的原料。布尔参数表示严格匹配(true)或部分匹配(false)。严格匹配要求数据组件完全相同;部分匹配要求指定组件相同,但允许存在其他组件。#of 还有其他重载,允许指定多个 Item 或提供其他选项。
  • DifferenceIngredient.of(Ingredient.of(BuiltInRegistries.ITEM.getOrThrow(ItemTags.PLANKS)), Ingredient.of(BuiltInRegistries.ITEM.getOrThrow(ItemTags.NON_FLAMMABLE_WOOD))) 返回匹配第一项但不匹配第二项的原料。本例只匹配可燃木板,即除绯红木板、诡异木板及模组下界木板之外的所有木板。
  • IntersectionIngredient.of(Ingredient.of(BuiltInRegistries.ITEM.getOrThrow(ItemTags.PLANKS)), Ingredient.of(BuiltInRegistries.ITEM.getOrThrow(ItemTags.NON_FLAMMABLE_WOOD))) 返回同时匹配两项子原料的原料。本例只匹配不可燃木板,即绯红木板、诡异木板及模组下界木板。

提示 若在数据生成中使用接收 HolderSet 作为标签实例的原料(调用 Registry#getOrThrow 的那些),则应通过 HolderLookup.Provider 获取该 HolderSet:先用 HolderLookup.Provider#lookupOrThrow 获取物品注册表,再向 HolderGetter#getOrThrow 传入 TagKey 获取 holder 集合。

注意,NeoForge 提供的原料类型都是 ICustomIngredient。如本文开头所述,将它们用于原版环境之前,必须调用 #toVanilla。

自定义原料类型

模组开发者可以通过 ICustomIngredient 系统添加自定义原料类型。这里以附魔物品原料为例,它接收物品标签,以及从附魔到最低等级的映射:

public class MinEnchantedIngredient implements ICustomIngredient {
    private final TagKey<Item> tag;
    private final Map<Holder<Enchantment>, Integer> enchantments;
    // The codec for serializing the ingredient.
    public static final MapCodec<MinEnchantedIngredient> CODEC = RecordCodecBuilder.mapCodec(inst -> inst.group(
            TagKey.codec(Registries.ITEM).fieldOf("tag").forGetter(e -> e.tag),
            Codec.unboundedMap(Enchantment.CODEC, Codec.INT)
                    .optionalFieldOf("enchantments", Map.of())
                    .forGetter(e -> e.enchantments)
    ).apply(inst, MinEnchantedIngredient::new));
    // Create a stream codec from the regular codec. In some cases, it might make sense to define
    // a new stream codec from scratch.
    public static final StreamCodec<RegistryFriendlyByteBuf, MinEnchantedIngredient> STREAM_CODEC =
            ByteBufCodecs.fromCodecWithRegistries(CODEC.codec());

    // Allow passing in a pre-existing map of enchantments to levels.
    public MinEnchantedIngredient(TagKey<Item> tag, Map<Holder<Enchantment>, Integer> enchantments) {
        this.tag = tag;
        this.enchantments = enchantments;
    }

    // Check if the passed ItemStack matches our ingredient by verifying the item is in the tag
    // and by testing for presence of all required enchantments with at least the required level.
    @Override
    public boolean test(ItemStack stack) {
        return stack.is(tag) && enchantments.keySet()
                .stream()
                .allMatch(ench -> EnchantmentHelper.getEnchantmentsForCrafting(stack).getLevel(ench) >= enchantments.get(ench));
    }

    // Determines whether this ingredient performs NBT or data component matching (false) or not (true).
    // Also determines whether a stream codec is used for syncing, more on this later.
    // We query enchantments on the stack, therefore our ingredient is not simple.
    @Override
    public boolean isSimple() {
        return false;
    }

    // Returns a stream of items that match this ingredient. Mostly for display purposes.
    // There's a few good practices to follow here:
    // - Always include at least one item, to prevent accidental recognition as empty.
    // - Include each accepted Item at least once.
    // - If #isSimple is true, this should be exact and contain every item that matches.
    //   If not, this should be as exact as possible, but doesn't need to be super accurate.
    // In our case, we use all items in the tag.
    @Override
    public Stream<Holder<Item>> items() {
        return BuiltInRegistries.ITEM.getOrThrow(tag).stream();
    }
}

自定义原料使用注册表,因此必须注册。使用 NeoForge 提供的 IngredientType 类即可,它本质上是 MapCodec 和可选 StreamCodec 的包装。

public static final DeferredRegister<IngredientType<?>> INGREDIENT_TYPES =
        DeferredRegister.create(NeoForgeRegistries.Keys.INGREDIENT_TYPE, ExampleMod.MOD_ID);

public static final Supplier<IngredientType<MinEnchantedIngredient>> MIN_ENCHANTED =
        INGREDIENT_TYPES.register("min_enchanted",
                // The stream codec parameter is optional, a stream codec will be created from the codec
                // using ByteBufCodecs#fromCodec or #fromCodecWithRegistries if the stream codec isn't specified.
                () -> new IngredientType<>(MinEnchantedIngredient.CODEC, MinEnchantedIngredient.STREAM_CODEC));

完成后,还需在原料类中重写 #getType:

public class MinEnchantedIngredient implements ICustomIngredient {
    // other stuff here

    @Override    
    public IngredientType<?> getType() {
        return MIN_ENCHANTED.get();
    }
}

这样,原料类型就可以使用了。

JSON 表示

由于原版原料功能比较有限,而 NeoForge 为此引入了全新注册表,因此有必要看看内置原料与自定义原料的 JSON 形式。

以对象形式表示且指定 neoforge:ingredient_type 的原料,通常视为非原版原料。例如:

{
    "neoforge:ingredient_type": "neoforge:block_tag",
    "tag": "minecraft:convertable_to_mud"
}

另一个使用自定义原料的例子:

{
    "neoforge:ingredient_type": "examplemod:min_enchanted",
    "tag": "c:swords",
    "enchantments": {
        "minecraft:sharpness": 4
    }
}

如果原料是字符串,即没有指定 neoforge:ingredient_type,则为原版原料。原版原料是表示物品的字符串,或加上 # 前缀来表示标签。

原版物品原料示例:

"minecraft:dirt"

原版标签原料示例:

"#c:ingots"

来源: NeoForged/Documentation(26.1)。中文翻译依据提交 4a680cdd4d15。