配方提供器

配方生成

继承 RecipeProvider 并实现 #buildRecipes,即可为模组生成配方。当消费者接受一个 FinishedRecipe 视图时,该配方就提供给数据生成。FinishedRecipe 可手动创建并提供,也可以方便地通过 RecipeBuilder 创建。实现后,必须将提供者添加到 DataGenerator。

RecipeProvider 在 buildRecipes 中构建配方;普通构建器设置有效 RecipeCategory 和额外解锁条件,通过 save 将 FinishedRecipe 视图交给消费者。

原创说明图,依据 Forge 1.21.x 官方文档 绘制。

// 在模组事件总线上
@SubscribeEvent
public void gatherData(GatherDataEvent event) {
  event.getGenerator().addProvider(
    event.includeServer(), // 仅生成服务端数据时运行
    MyRecipeProvider::new
  );
}

RecipeBuilder

RecipeBuilder 是方便创建待生成 FinishedRecipe 的实现。它通过 #unlockedBy、#group、#save 和 #getResult 分别提供解锁、分组、保存及获取配方结果的基本定义。

重要:原版配方构建器不支持配方中的 ItemStack 输出。要用现有原版配方序列化器生成此类数据,必须以其他方式构建 FinishedRecipe。

警告:生成的物品结果必须指定有效的 RecipeCategory,否则会抛出 NullPointerException。

除 SpecialRecipeBuilder 外,所有配方构建器都要求指定进度条件。所有配方都会生成一个条件:玩家此前使用过该配方时解锁它。但还必须指定一个额外条件,让玩家无需事先知道配方即可获得它。指定条件中任意一个为真时,玩家便在配方书中获得该配方。

提示:配方条件通常使用 InventoryChangeTrigger,在玩家物品栏存在某些物品时解锁配方。

ShapedRecipeBuilder

ShapedRecipeBuilder 生成有序配方,通过 #shaped 初始化。保存前可指定配方组、输入符号图案、符号对应的原料以及解锁条件。

// 在 RecipeProvider#buildRecipes(writer) 中
ShapedRecipeBuilder builder = ShapedRecipeBuilder.shaped(RecipeCategory.MISC, result)
  .pattern("a a") // 创建配方图案
  .define('a', item) // 定义符号代表的物品
  .unlockedBy("criteria", criteria) // 解锁方式
  .save(writer); // 将数据添加到 builder
额外验证

有序配方构建前还会进行以下验证:

  • 必须定义图案,且包含多个物品。
  • 所有图案行宽度必须一致。
  • 同一符号不能定义多次。
  • 空格字符(' ')保留用于表示空槽位,不能定义。
  • 图案必须使用用户定义的所有符号。

ShapelessRecipeBuilder

ShapelessRecipeBuilder 生成无序配方,通过 #shapeless 初始化。保存前可指定组、输入原料和解锁条件。

// 在 RecipeProvider#buildRecipes(writer) 中
ShapelessRecipeBuilder builder = ShapelessRecipeBuilder.shapeless(RecipeCategory.MISC, result)
  .requires(item) // 添加物品
  .unlockedBy("criteria", criteria) // 配方解锁方式
  .save(writer); // 将数据交给写入器

SimpleCookingRecipeBuilder

该构建器生成熔炼、高炉、烟熏和营火烹饪配方;使用 SimpleCookingSerializer 的自定义烹饪配方也可使用它生成。分别通过 #smelting、#blasting、#smoking、#campfireCooking 或 #cooking 初始化,保存前可指定组和解锁条件。

// 在 RecipeProvider#buildRecipes(writer) 中
SimpleCookingRecipeBuilder builder = SimpleCookingRecipeBuilder.smelting(input, RecipeCategory.MISC, result, experience, cookingTime)
  .unlockedBy("criteria", criteria) // 配方解锁方式
  .save(writer); // 将数据交给写入器

SingleItemRecipeBuilder

该构建器生成切石配方,也可生成使用类似 SingleItemRecipe$Serializer 序列化器的自定义单物品配方。分别通过 #stonecutting 或构造函数初始化,保存前可指定组和解锁条件。

// 在 RecipeProvider#buildRecipes(writer) 中
SingleItemRecipeBuilder builder = SingleItemRecipeBuilder.stonecutting(input, RecipeCategory.MISC, result)
  .unlockedBy("criteria", criteria) // 配方解锁方式
  .save(writer); // 将数据交给写入器

不实现 RecipeBuilder 的构建器

一些配方构建器因缺少上述配方共有的功能而不实现 RecipeBuilder。

SmithingTransformRecipeBuilder

用于生成转换物品的锻造配方,也可生成使用类似 SmithingTransformRecipe$Serializer 的自定义配方。分别通过 #smithing 或构造函数初始化;保存前可指定解锁条件。

// 在 RecipeProvider#buildRecipes(writer) 中
SmithingTransformRecipeBuilder builder = SmithingTransformRecipeBuilder.smithing(template, base, addition, RecipeCategory.MISC, result)
  .unlocks("criteria", criteria) // 配方解锁方式
  .save(writer, name); // 将数据交给写入器

SmithingTrimRecipeBuilder

用于生成盔甲纹饰的锻造配方,也可生成使用类似 SmithingTrimRecipe$Serializer 的自定义升级配方。分别通过 #smithingTrim 或构造函数初始化;保存前可指定解锁条件。

// 在 RecipeProvider#buildRecipes(writer) 中
SmithingTrimRecipeBuilder builder = SmithingTrimRecipeBuilder.smithingTrim(template, base, addition, RecipeCategory.MISC)
  .unlocks("criteria", criteria) // 配方解锁方式
  .save(writer, name); // 将数据交给写入器

原文示例将类写成 SmithingTrimRecipe,与本节所述构建器不一致;译文改为 SmithingTrimRecipeBuilder。下列代码沿用原文的链式写法,具体 #save 签名应以目标 Forge 版本为准。

SpecialRecipeBuilder

用于为不易限制在配方 JSON 格式内的动态配方生成空 JSON,例如盔甲染色和烟花。通过 #special 初始化。

// 在 RecipeProvider#buildRecipes(writer) 中
SpecialRecipeBuilder.special(dynamicRecipeSerializer)
  .save(writer, name); // 将数据添加到构建器

条件配方

也可通过 ConditionalRecipe$Builder 生成条件配方,使用 #builder 获取构建器。

先调用 #addCondition 指定每个配方的条件,在全部条件指定后调用 #addRecipe。这一过程可以重复任意次数。指定所有配方后,可在末尾使用 #generateAdvancement 为每个配方添加进度;也可使用 #setAdvancement 设置条件进度。

// 在 RecipeProvider#buildRecipes(writer) 中
ConditionalRecipe.builder()
  .addCondition(...) // 添加配方条件
  .addRecipe(...) // 条件为真时返回的配方
  .addCondition(...) // 下一配方的条件
  .addRecipe(...) // 条件为真时返回的下一配方
  .generateAdvancement() // 使用上述配方的条件和解锁进度创建条件进度
  .build(writer, name);

IConditionBuilder

为方便向条件配方添加条件,无需手动构造每个条件实例,继承的 RecipeProvider 可实现 IConditionBuilder。该接口添加了方便构造条件实例的方法。

// 在 ConditionalRecipe$Builder#addCondition 中:
// examplemod:example_item 或 examplemod:example_item2 存在,且 NOT FALSE
// 以下方法由 IConditionBuilder 定义
(
  and(
    or(itemExists("examplemod", "example_item"), itemExists("examplemod", "example_item2")),
    not(FALSE())
  )
)

自定义配方序列化器

通过创建能构造 FinishedRecipe 的构建器,可以生成自定义配方序列化器的数据。完成的配方将配方数据和存在时的解锁进度编码为 JSON,还指定配方名称及序列化器,以确定写入位置及加载时由什么解码对象。构造完成后,只需将 FinishedRecipe 传入 RecipeProvider#buildRecipes 提供的 Consumer。

提示:FinishedRecipe 足够灵活,可生成任何对象转换的数据,而不限于物品。

原文:Recipe Generation。