自定义配方处理逻辑
这篇文档对应的使用场景是:
- 默认的“输入价值求和”不适合你的配方类型
- 你要处理多产物、自定义 RecipeType、额外工序成本
- 你希望对配方生成值做全局修正
先明确默认行为的边界
模组默认的配方处理器做的事情很简单:
- 取配方输入。
- 计算输入价值总和。
- 取输出列表中的第一个物品。
- 把总价值赋给该输出,并按堆叠数量平摊。
所以一旦你的配方不满足这种“单产物、直接求和”的结构,就应该考虑自定义。
入口事件
配方扩展都在这个事件里完成:
OEVEvents.addRecipeHandler(event => {
})
场景一:给新的 RecipeType 启用默认处理
如果某个模组新加了一种配方类型,但它本质上仍然是普通输入求和,那最省事的做法就是直接注册默认处理器。
let $RecipeType = Java.loadClass('net.minecraft.world.item.crafting.RecipeType')
OEVEvents.addRecipeHandler(event => {
event.addSimpleRecipeHandler($RecipeType.BLASTING)
})
这适用于:
- 只是默认列表里没包含的 RecipeType
- 但其输入输出结构仍然普通
场景二:完全自定义处理器
当你需要自己控制输入、输出、额外成本和赋值规则时,使用 addCustomRecipeHandler。
let $RecipeType = Java.loadClass('net.minecraft.world.item.crafting.RecipeType')
OEVEvents.addRecipeHandler(event => {
event.addCustomRecipeHandler(
$RecipeType.CRAFTING,
event.defaultRecipeInputGetter,
event.defaultRecipeOutputGetter,
event.defaultRecipeExtraValueGetter,
(recipe, stacks, totalValue, setter) => {
setter.set(recipe, stacks[0], totalValue * 10)
}
)
})
上面 这个例子会把配方结果价值改成输入总成本的十倍。
虽然只是演示,但已经能说明五个参数分别负责什么。
五个参数怎么理解
1. RecipeType
指定你正在处理哪一种配方类型。
2. inputGetter
List<Ingredient> get(Recipe<?> recipe);
负责告诉模组“这个配方有哪些输入 Ingredient”。
如果配方本身结构正常,直接用默认的 event.defaultRecipeInputGetter 就够了。
3. outputGetter
List<ItemStack> get(Recipe<?> recipe, RegistryAccess registryAccess);
负责告诉模组“这个配方产出哪些物品”。
默认逻辑只会从标准结果位里取一个输出。
如果某类配方会同时产出多个物品,或者输出不在标准位置,就要自己改这里。
4. extraValueGetter
int get(Recipe<?> recipe);
负责添加“配方额外成本”。
这很适合处理下面这些情况:
- 熔炼需要燃料
- 某种工序有固定手续费
- 配方要消耗某个逻辑上不写在输入里的额外资源
5. valueSetter
void accept(Recipe<?> recipe, List<ItemStack> stacks, int totalValue, IRecipeGenValueSetter setter);
负责把总价值真正分配给输出。
默认情况下只会给第一个输出设置值。
如果你有多输出,就必须自己决定怎么分配。
场景三:给某类工序增加统一成本
如果你只是想对生成后的结果统一修正,而不是整套重写处理逻辑,可以使用:
OEVEvents.addRecipeHandler(event => {
event.modifyRecipeGenValue((type, oldValue) => {
if (type === 'minecraft:smelting') return oldValue + 64
return oldValue
})
})
这个回调会在配方值写入前执行。
适合做:
- 熔炼燃料成本
- 机器加工税
- 某类工序固定损耗
- 整体倍率修正
场景四:处理特殊 Ingredient
有些模组会使用自定义 Ingredient 类,而默认 getItems() 的结果可能不完整,或者根本不代表真实输入范围。
此时可以注册特殊原料处理器:
OEVEvents.addRecipeHandler(event => {
event.register(SomeIngredientClass, ingredient => {
return 1024
})
})
这个接口属于高级联动能力,适合:
- 你清楚目标模组 Ingredient 的实现
- 你知道默认逻辑为什么算不对
- 你希望给这种 Ingredient 单独定义取值规则
如何排查“配方类型到底叫什么”
模组提供了两个辅助方法:
OEVEvents.addRecipeHandler(event => {
console.log(event.getAllRecipeType())
console.log(event.getAllRecipeTypeName())
})
它们可以帮助你先把已注册的配方类型枚举出来,再决定对哪一种做处理。
多输出时的思路建议
如果一个配方会同时得到多个输出,最重要的问题不是“能不能算”,而是“怎么分才合理”。
常见策略有三种:
1. 主产物吃掉绝大多数价值
适合副产物只是顺带给一点补偿的场景。
2. 按你预设的比例拆分
适合你已经有明确设计意图的机器链。
3. 只给一个输出定值,剩下输出视为无价值副产物
适合你想尽量避免经济系统被副产物刷爆。
实战建议
当你开始写自定义处理器时,建议不要同时改很多件事。
更稳妥的顺序是:
- 先确认配方类型名。
- 先尝试
addSimpleRecipeHandler看能否满足。 - 不够再改
addCustomRecipeHandler。 - 最后再考虑特殊 Ingredient 处理。
这样排错成本最低。