Skip to main content
Version: 1.21.4

配料(Ingredients)

Ingredient(配料)用于在 配方 中判断某个 ItemStack 是否为该配方的有效输入。为此,Ingredient 实现了 Predicate<ItemStack> 接口,可以通过调用 #test 方法来确认给定的 ItemStack 是否符合该配料的要求。

不幸的是,Ingredient 的许多内部实现相当混乱。NeoForge 通过在可能的情况下忽略 Ingredient 类来规避这一问题,而是引入了用于自定义配料的 ICustomIngredient 接口。这个接口并不是常规 Ingredient 的直接替代品,但可以通过 ICustomIngredient#toVanillaIngredient#getCustomIngredient 在二者之间进行转换。

内置配料类型(Built-In Ingredient Types)

获取配料最简单的方式是使用 Ingredient#of 辅助方法。它有几种变体:

  • Ingredient.of() 返回一个空配料。
  • Ingredient.of(Blocks.IRON_BLOCK, Items.GOLD_BLOCK) 返回一个可以接受铁块或金块的配料。这个参数是个可变参数(vararg),类型为 ItemLike,意味着你可以传入任意数量的方块或物品。
  • Ingredient.of(Stream.of(Items.DIAMOND_SWORD)) 返回一个可以接受指定物品的配料。与前一个方法类似,但这里使用的是 Stream<ItemLike>,适合你手头已经有一个流对象的情况。
  • Ingredient.of(BuiltInRegistries.ITEM.getOrThrow(ItemTags.WOODEN_SLABS)) 返回一个可以接受指定 标签 下任意物品的配料,例如任意木质台阶。

此外,NeoForge 还新增了一些额外的配料类型:

  • BlockTagIngredient.of(BlockTags.CONVERTABLE_TO_MUD) 返回一个类似于 Ingredient.of() 标签变体的配料(ingredient),但这里使用的是方块标签(block tag)而非物品标签(item tag)。当你需要使用物品标签,但只有方块标签可用时(例如 minecraft:convertable_to_mud),应当使用这种方式。
  • CustomDisplayIngredient.of(Ingredient.of(Items.DIRT), SlotDisplay.Empty.INSTANCE) 返回一个带有自定义 SlotDisplay 的配料,你可以通过自定义 SlotDisplay 来决定该槽位在客户端渲染时如何显示和消耗。
  • CompoundIngredient.of(Ingredient.of(Items.DIRT)) 返回一个拥有子配料(child ingredient)的配料,这些子配料通过构造函数的可变参数(vararg parameter)传入。只要其任意一个子配料匹配,该配料即视为匹配。
  • DataComponentIngredient.of(true, new ItemStack(Items.DIAMOND_SWORD)) 返回一个除了匹配物品外,还要求数据组件(data component)匹配的配料。布尔参数表示是否严格匹配(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))) 返回一个配料,匹配同时满足两个子配料的物品。上面的例子只会匹配不能燃烧的木板(即猩红木板、诡异木板和模组添加的下界木板)。
note

如果你在数据生成(data generation)时使用了需要 HolderSet 作为标签实例(tag instance)的配料(即那些调用 Registry#getOrThrow 的配料),那么这个 HolderSet 应该通过 HolderLookup.Provider 获取。具体来说,先用 HolderLookup.Provider#lookupOrThrow 获取物品注册表(item registry),再用 HolderGetter#getOrThrowTagKey 获取 holder set。

请注意,NeoForge 提供的配料类型都是 ICustomIngredient,在用于原版(vanilla)相关场景前,必须调用 #toVanilla 方法,正如本文开头所述。

自定义配料类型(Custom Ingredient Types)

模组开发者可以通过 ICustomIngredient 系统添加自定义的配料类型。举个例子,假设我们要实现一个附魔物品配料(enchanted item ingredient),它接受一个物品标签(item tag)和一个附魔(enchantment)到最小等级(min level)的映射:

public class MinEnchantedIngredient implements ICustomIngredient {
private final TagKey<Item> tag;
private final Map<Holder<Enchantment>, Integer> enchantments;
// 用于序列化该配料(ingredient)的编解码器(codec)。
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));
// 从常规 codec 创建一个流式编解码器(stream codec)。在某些情况下,也可以直接从头定义新的流式编解码器。
public static final StreamCodec<RegistryFriendlyByteBuf, MinEnchantedIngredient> STREAM_CODEC =
ByteBufCodecs.fromCodecWithRegistries(CODEC.codec());

// 允许传入一个已存在的附魔(enchantment)及其等级的映射。
public MinEnchantedIngredient(TagKey<Item> tag, Map<Holder<Enchantment>, Integer> enchantments) {
this.tag = tag;
this.enchantments = enchantments;
}

// 通过验证物品是否属于指定标签(tag),并检查所有要求的附魔是否存在且等级不低于要求,判断传入的 ItemStack 是否匹配我们的配料。
@Override
public boolean test(ItemStack stack) {
return stack.is(tag) && enchantments.keySet()
.stream()
.allMatch(ench -> EnchantmentHelper.getEnchantmentsForCrafting(stack).getLevel(ench) >= enchantments.get(ench));
}

// 判断该配料是否执行 NBT 或数据组件匹配(返回 false),或者不是(返回 true)。
// 这也决定了是否使用流式编解码器进行同步,后面会详细介绍。
// 我们需要查询物品堆(stack)上的附魔,因此该配料不是简单类型。
@Override
public boolean isSimple() {
return false;
}

// 返回一个匹配该配料的物品流。主要用于显示目的。
// 这里有一些最佳实践:
// - 始终至少包含一个物品,以防被意外识别为空。
// - 每个被接受的物品至少包含一次。
// - 如果 #isSimple 返回 true,这里应精确包含所有匹配的物品。
// 如果不是,则尽量精确,但不必完全准确。
// 在我们的案例中,包含标签(tag)下的所有物品。
@Override
public Stream<Holder<Item>> getItems() {
return BuiltInRegistries.ITEM.getOrThrow(tag).stream();
}
}

自定义配料(custom ingredient)本质上是一个注册表(registry),因此我们必须注册自己的配料类型。我们可以使用 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 {
// 这里省略其他内容

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

就是这样!我们的配料类型(ingredient type)已经可以使用了。

JSON 表示方式(JSON Representation)

由于原版 Minecraft 的配料(ingredient)功能相当有限,而 NeoForge 为其引入了全新的注册表(Registry),因此了解内置和自定义配料在 JSON 中的表现形式也很有价值。

如果配料是一个对象,并且指定了 neoforge:ingredient_type,通常会被认为是非原版(non-vanilla)的配料。例如:

{
"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,那么它就是一个原版配料(vanilla ingredient)。原版配料可以是一个字符串,表示某个物品(item),或者以 # 开头时表示标签(tag)。

一个原版物品配料的例子:

"minecraft:dirt"

一个原版标签配料的例子:

"#c:ingots"

编解码器
类似物品
物品堆栈
配方
注册表
槽位显示
流式编解码器
标签