自定义配方

自定义配方

每个配方定义由三个部分组成:保存数据并根据输入处理执行逻辑的 Recipe 实现;表示配方使用类别或上下文的 RecipeType;负责解码和网络通信的 RecipeSerializer。如何使用配方由实现者决定。

自定义配方三部分关系:RecipeSerializer 解码 JSON 并负责网络通信,Recipe 保存数据和实现匹配及结果逻辑,RecipeType 定义使用类别;输入视为不可变,assemble 返回独立物品堆。

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

Recipe

Recipe 接口描述配方数据及执行逻辑,包括匹配输入并提供相应结果。配方子系统默认执行物品转换,因此通过 Container 子类型提供输入。

重要:传入配方的 Container 内容应视为不可变。任何修改操作都应通过 ItemStack#copy 对输入副本执行。

要从管理器获取配方实例,#matches 必须返回 true。该方法检查容器中的输入是否有效,可以调用 Ingredient#test 使用配料验证。选中配方后,使用 #assemble 构建结果,可以利用输入数据创建结果。

提示:#assemble 应始终产生独立的 ItemStack。不确定时,返回前对结果调用 ItemStack#copy。

其他方法大多用于配方书集成。

public record ExampleRecipe(Ingredient input, int data, ItemStack output) implements Recipe<Container> {
  // 在此实现方法
}

上例使用了 record,但自己的实现不一定要使用。

RecipeType

RecipeType 定义配方使用的类别或上下文。例如熔炉烧炼使用 RecipeType#SMELTING,高炉烧炼使用 RecipeType#BLASTING。如果现有类型均不匹配,应注册新类型,并在新配方子类型的 Recipe#getType 中返回实例。

// 对于某个 RegistryObject<RecipeType> EXAMPLE_TYPE,在 ExampleRecipe 中:
@Override
public RecipeType<?> getType() {
  return EXAMPLE_TYPE.get();
}

RecipeSerializer

RecipeSerializer 负责为对应 Recipe 子类型解码 JSON 和进行网络通信。每个解码后的配方都作为独立实例存储在 RecipeManager 中。序列化器必须注册。

只需实现三个方法:

方法 说明
fromJson 将 JSON 解码为 Recipe 子类型。
toNetwork 将配方编码到缓冲区以发送给客户端,无需编码配方标识符。
fromNetwork 从服务端发送的缓冲区解码配方,无需解码配方标识符。

新配方子类型的 Recipe#getSerializer 必须返回该实例:

// 对于某个 RegistryObject<RecipeSerializer> EXAMPLE_SERIALIZER,在 ExampleRecipe 中:
@Override
public RecipeSerializer<?> getSerializer() {
  return EXAMPLE_SERIALIZER.get();
}

提示:有些方法可以方便地读写配方数据。Ingredient 可用 #fromJson、#toNetwork、#fromNetwork;ItemStack 可用 CraftingHelper#getItemStack、FriendlyByteBuf#writeItem、FriendlyByteBuf#readItem。

构建 JSON

自定义配方 JSON 与其他配方存放在同一位置。type 应为配方序列化器的注册表名称;额外数据由序列化器在解码时指定。

带注释和省略号的示例用于说明结构;实际 JSON 文件需移除注释并补全内容。

{
  "type": "examplemod:example_serializer", // 自定义序列化器注册表名称
  "input": { /* 某个配料输入 */ },
  "data": 0, // 配方所需的某些数据
  "output": { /* 某个输出堆 */ }
}

非物品逻辑

如果配方输入或结果不使用物品,RecipeManager 提供的常规方法便无用。应在自定义 Recipe 实例中添加检查配方有效性和/或提供结果的方法。随后通过 RecipeManager#getAllRecipesFor 获取特定 RecipeType 的所有配方,用新方法检查并提供结果。

// 在某个 Recipe 实现 ExampleRecipe 中
// 检查指定位置的方块是否匹配保存的数据
boolean matches(Level level, BlockPos pos);

// 创建要设置在指定位置的方块状态
BlockState assemble(RegistryAccess access);

// 在某个管理器类中
public Optional<ExampleRecipe> getRecipeFor(Level level, BlockPos pos) {
  return level.getRecipeManager()
    .getAllRecipesFor(exampleRecipeType) // 获取所有配方
    .stream() // 遍历类型的所有配方
    .filter(recipe -> recipe.matches(level, pos)) // 检查输入是否有效
    .findFirst(); // 找到输入匹配的第一个配方
}

数据生成

无论输入或输出是什么,所有自定义配方均可通过 RecipeProvider 创建为 FinishedRecipe,用于数据生成。

原文:Custom Recipes。