Skip to content

配方系统 ​

EmakiCooking 的配方按工位 (世界里用来做菜的那块方块) 分类放在 recipes/<station>/ 里, 每类工位的字段和交互都不一样

你要改哪类工位的配方, 就进对应的目录: plugins/EmakiCooking/recipes/<station>/. 每类工位的交互不同, 字段也不同: 砧板按输入数量与切割次数, 炒锅按翻炒与火候, 烤炉按烘烤阶段, 榨汁机按流体容量, 发酵桶按时间阶段

配方目录 ​

目录工位典型玩法
recipes/chopping_board/砧板单输入, 一次收下手中的整叠物品, 按输入数量与切割次数产出切片或半成品
recipes/wok/炒锅多输入, 结果由火候, 翻炒次数和容错决定
recipes/grinder/研磨机放进材料, 等研磨时间到了就产出粉末
recipes/steamer/蒸锅放入材料, 按蒸制时间或链式步骤产出熟制品
recipes/oven/烤炉单输入, 结果由烘烤时间, 完美火力比例和过烤阶段决定
recipes/juicer/榨汁机单输入, 多次压榨生成流体, 再按容器容量盛取
recipes/fermentation_barrel/发酵桶多输入, 结果由发酵时间, 提前收取和过度发酵决定

当前推荐写法 ​

当前默认资源统一使用 CoreLib Item Source 输入列表, 单值产出字段和结果分支结构:

yaml
result:
  success:
    outputs:
      - item_source: "minecraft-baked_potato"
        amount: 1
        
    actions:
      - 'send_message text="<gold>料理完成。</gold>"'

关键约定:

  • 输入侧写 item_sources 列表, 例如 minecraft-carrot, minecraft-glass_bottle
  • 产出侧写单值 item_source, 例如 item_source: "minecraft-baked_potato". 旧写法 item_sources 仍被读取, 但必须恰好包含一个来源, 否则该产物被丢弃并告警; 两键同时出现也会告警并丢弃该产物. 产出节点上写 matcher 会被拒绝并丢弃
  • result.<branch>.outputs 永远是列表, 只有一个产物也写列表
  • result.<branch>.actions 与 outputs 同级, 位于同一个结果分支下
  • 不再使用旧示例里的 output, result.output, result.outputs, result.actions, perfect_output, overbaked_output, fermentation.early_collect.output, fermentation.over_output

通用字段 ​

字段必填说明
id是配方唯一的 ID, 建议和文件名一致
display_name是展示名称. 支持 MiniMessage
permission否使用该配方所需权限; 为空表示不限制
condition否完成或收取时判定的条件块, 可以配 on_pass.actions, on_fail.actions 和 on_fail.block_output
availability_condition否配方是否可用 (能否被匹配到) 的条件块. 与 condition 不同: 它在匹配阶段判定, 不通过这个配方就直接不参与匹配
structured_presentation否配方级结构化展示配置. 产物条目下也可写一份 outputs[].structured_presentation, 两层会合并, 产物级覆盖配方级
result.<branch>.outputs是当前分支产物列表
result.<branch>.outputs[].item_source是该产物的单个物品来源. 旧写法 item_sources 仍被读取, 但必须恰好包含一个来源
result.<branch>.outputs[].amount否该产物数量, 默认 1, 小于 1 按 1 处理
result.<branch>.outputs[].chance否该产物的掉落概率, 取值 0~100, 默认 100. <=0 恒不发放, >=100 恒发放. 逐个产物独立判定
result.<branch>.outputs[].amount_range否产物数量随机区间, 写 min / max 两个子键, 默认 min=1, max=min. 存在时覆盖 amount; min > max 会自动交换
result.<branch>.actions否当前分支产出时执行的动作列表

通用物品匹配器 (matcher) ​

Cooking 的输入侧字段都支持 matcher, 按组件, PDC, Lore 等条件进一步收窄物品判定. 完整语法 (类型全集, operator, path, 嵌套写法) 见 物品匹配器

item_sources 与 matcher 是同级并行字段

所有输入判定位都由这两个字段合成, 取 AND:

  • 只写 item_sources: 按物品源判定, 不加额外条件. 文中大量裸 item_sources 示例都是这种形态
  • 只写 matcher: 只按组件 / PDC / Lore 等条件判定, 不限来源
  • 两者都写: 必须同时成立
  • 两者都不写: 该条目永不命中 (配方会被跳过)

matcher 内部不能写物品源条件. type: item_source / item_sources / source / sources 会被解析器拒绝并告警, 该 matcher 恒不匹配. 物品源一律写到同级的 item_sources

工位角色侧 (工具, 锅铲, 工位容器, 加水规则输入) 现在是嵌套子节点, 每个子节点下同样是标准的 item_sources + matcher: stations.chopping_board.tool, stations.wok.spatula, stations.juicer.container, moisture_rules[].input. 旧的扁平前缀键 (tool_matcher, spatula_matcher, container_matcher, input_matcher) 仍被加载期兼容读取, 但新配置请写嵌套子节点. 详见 工位系统

配置位置说明
ingredients[].matcher炒锅配方原料
inputs[].matcher发酵桶多输入项
input.matcher单输入配方的输入项
container.matcher容器物品 (如瓶子)
stations.chopping_board.tool.matcher工具识别 (config.yml)
stations.wok.spatula.matcher锅铲识别 (config.yml)
stations.juicer.container.matcher工位容器识别 (config.yml)
stations.oven.fuels[].matcher烤炉燃料 (config.yml)
stations.steamer.fuels[].matcher蒸锅燃料 (config.yml)
stations.steamer.moisture_rules[].input.matcher蒸锅湿度规则输入 (config.yml)
nutrition.food_sources[].matcher食物到营养的映射 (config.yml, 见营养系统)

表中每一项都与同级的一个 item_sources 配对

yaml
id: fresh_carrot_slice
ingredients:
  # "是胡萝卜"写 item_sources, "没被重命名过"写 matcher, 两者 AND
  - item_sources:
      - "minecraft-carrot"
    matcher:
      type: component
      component: custom_name
      operator: absent
    amount: 1
result:
  success:
    outputs:
      - item_source: "minecraft-golden_carrot"
        amount: 1

DANGER

方块侧与产出侧不支持 matcher

  • 工位方块识别 (block_item_sources 一类字段) 是"哪个方块算这个工位", 不是输入匹配
  • 产出侧 (result.<branch>.outputs[].item_sources) 是"生成什么物品", 属于构造而非匹配

这两处写 matcher 不会生效. 详见 只有输入匹配点支持 matcher

结果分支 ​

工位常用结果分支
砧板success
研磨机success
蒸锅success, 链式配方可按步骤继续处理
炒锅success, undercooked, overcooked, invalid
烤炉success, perfect, overbaked
榨汁机success
发酵桶success, early, over

condition.on_fail.block_output 为 true 时, 条件不满足会阻止产物输出; 为 false 时仍可输出, 但会执行失败动作. 发酵桶在自动完成或破坏掉落等玩家可能离线的场景下, 条件处理以源码运行时逻辑为准

砧板示例 ​

yaml
id: "cut_carrot"
display_name: "切制胡萝卜"

input:
  item_sources:
    - "minecraft-carrot"
  amount: 2

cuts_required: 1
tool_damage: 1

result:
  success:
    outputs:
      - item_source: "minecraft-golden_carrot"
        amount: 1
    actions:
      - 'send_message text="<green>砧板处理完成。</green>"'
字段说明
input.item_sources必填 (与 input.matcher 二者至少写一个) 可匹配的输入来源
input.matcher可选. 只写组件 / PDC / Lore 等非物品源条件, 与同级 input.item_sources 取 AND
input.amount每次完成切割需要并消耗的输入数量; 未配置时按 1 处理, 小于 1 按 1 处理
cuts_required必填. 完成一轮切割所需点击次数
tool_damage每次切割对工具造成的耐久损耗, 默认 1, 小于 1 按 1 处理
damage_override.chance覆盖 config.yml > stations.chopping_board.cut_damage.chance, 即本配方切割时扣耐久的概率
damage_override.value覆盖 config.yml > stations.chopping_board.cut_damage.value, 即本配方每次扣除的耐久值

damage_override 是可选段; 未写时使用 config.yml 中的全局 cut_damage 设置

砧板放入输入时会一次收下玩家主手那一整叠, 并在工位状态中累计数量; 展示实体仍然只显示 1 个物品. 累计数量不足 input.amount 时不能开始切割. 若设置 cuts_required: 1, 玩家可以对已经放入的一批食材连续完成多轮切割, 不用每次重新放入 1 个输入

炒锅示例 ​

yaml
id: "example_recipe"
display_name: "简易炖菜"

ingredients:
  - item_sources:
      - "minecraft-carrot"
    amount: 1
    stir_rule: "1-3"
  - item_sources:
      - "minecraft-cooked_chicken"
    amount: 1
    stir_rule: "2-3"

heat_level: 1

stir_total:
  min: 2
  max: 5

fault_tolerance: 0
permission: "emakicooking.recipe.simple_stew"

condition:
  type: all_of
  entries:
    - "%player_level% >= 5"
  on_pass:
    actions:
      - 'send_message text="<green>厨艺达标,出锅成功。</green>"'
  on_fail:
    actions:
      - 'send_message text="<red>厨艺等级不足,出锅失败。</red>"'
    block_output: true

result:
  success:
    outputs:
      - item_source: "minecraft-rabbit_stew"
        amount: 1
    actions:
      - 'send_message text="<green>成功出锅。</green>"'
  undercooked:
    outputs:
      - item_source: "minecraft-mushroom_stew"
        amount: 1
    actions: []
  overcooked:
    outputs:
      - item_source: "minecraft-dried_kelp"
        amount: 1
    actions: []
  invalid:
    outputs:
      - item_source: "minecraft-stone"
        amount: 1
    actions: []
字段说明
ingredients必填. 食材列表, 缺失则该配方拒绝载入
ingredients[].item_sources可匹配的食材来源
ingredients[].amount需要数量
ingredients[].stir_rule建议加入或翻炒区间, 例如 1-3
heat_level必填. 配方要求的火力等级
stir_total.min/max两个子键都必填. 有效总翻炒次数范围
fault_tolerance必填. 允许错误次数

研磨机示例 ​

yaml
id: "bone_meal"
display_name: "骨粉研磨"

input:
  item_sources:
    - "minecraft-bone"

grind_time_seconds: 6

permission: "emakicooking.recipe.bone_meal"

result:
  success:
    outputs:
      - item_source: "minecraft-bone_meal"
        amount: 3
    actions:
      - 'send_message text="<gray>研磨结束。</gray>"'
字段说明
input.item_sources必填 (与 input.matcher 二者至少写一个) 可匹配的输入来源
grind_time_seconds必填. 研磨所需秒数. 研磨机按 config.yml > stations.grinder.check_delay_ticks 周期推进

蒸锅示例 ​

yaml
id: "steamed_cod"
display_name: "清蒸鳕鱼"

input:
  item_sources:
    - "minecraft-cod"

required_steam: 40

permission: "emakicooking.recipe.steamed_cod"

result:
  success:
    outputs:
      - item_source: "minecraft-cooked_cod"
        amount: 1
    actions:
      - 'send_message text="<aqua>蒸制完成。</aqua>"'
字段说明
input.item_sources必填 (与 input.matcher 二者至少写一个) 可匹配的输入来源
required_steam必填. 完成该配方需要累计消耗的蒸汽量
requires_previous_step可选. 填写另一个蒸锅配方 ID, 表示本配方是该配方的后续步骤, 用于链式蒸制

链式示例: recipes/steamer/chain_example_recipe.yml 用 requires_previous_step: "example_recipe" 把上一步产物接到下一步

烤炉示例 ​

yaml
id: "baked_potato"
display_name: "烤马铃薯"

input:
  item_sources:
    - "minecraft-potato"

bake_time_seconds: 20

baking:
  perfect_heat:
    min: 45
    max: 60
  perfect_required_ratio: 0.7
  overbake_seconds: 10

permission: "emakicooking.recipe.baked_potato"

result:
  success:
    outputs:
      - item_source: "minecraft-baked_potato"
        amount: 1
    actions:
      - 'send_message text="<gold>烤制完成。</gold>"'
  perfect:
    outputs:
      - item_source: "minecraft-golden_carrot"
        amount: 1
  overbaked:
    outputs:
      - item_source: "minecraft-charcoal"
        amount: 1

烤炉配方的必填字段是 id, display_name, input.item_sources (或 input.matcher) 和 bake_time_seconds, 缺任意一项该配方直接拒绝载入. baking 段为可选

字段必填默认值说明
bake_time_seconds是—正常烘烤所需秒数, 小于 0 按 0 处理
baking.perfect_heat.min否config.yml > stations.oven.heat.min (默认 20)完美火力区间下限
baking.perfect_heat.max否config.yml > stations.oven.heat.max (默认 80)完美火力区间上限; 小于下限时被抬到下限
baking.perfect_required_ratio否1.0判定为完美所需的处于完美火力区间的比例, 截断到 0~1
baking.overbake_seconds否0正常完成后继续加热进入过烤的秒数

烤炉会根据烘烤时间, 处于完美火力区间的比例, 以及完成后继续加热时间决定结果. 高价值料理建议给玩家清楚展示火力和进度, 避免"看不懂为什么过烤"

榨汁机示例 ​

yaml
id: "apple_juice"
display_name: "苹果汁"

input:
  item_sources:
    - "minecraft-apple"

presses_required: 5

fluid:
  id: "apple_juice"
  display_name: "苹果汁"
  amount_ml: 180

container:
  item_sources:
    - "minecraft-glass_bottle"
  serving_ml: 250

permission: "emakicooking.recipe.apple_juice"

result:
  success:
    outputs:
      - item_source: "minecraft-honey_bottle"
        amount: 1
    actions:
      - 'send_message text="<aqua>榨汁完成。</aqua>"'

榨汁机配方的必填字段是 id, display_name, input.item_sources (或 input.matcher) 和 presses_required, 缺任意一项该配方直接拒绝载入

字段必填默认值说明
presses_required是—完成压榨所需次数, 小于 0 按 0 处理
fluid.id否""流体 ID. 为空表示该配方不产出流体
fluid.display_name否等于 fluid.id流体显示名
fluid.amount_ml否0每次压榨加入的流体量 (ml), 小于 0 按 0 处理
container.item_sources否—盛取容器的物品来源; 与同级 container.matcher 取 AND
container.matcher否无盛取容器的附加条件, 只写组件 / PDC / Lore 等非物品源条件
container.serving_ml否config.yml > stations.juicer.default_serving_ml (默认 250)每次盛取消耗的流体量 (ml), 小于 1 按 1 处理

一次压榨会向工位加入 fluid.amount_ml 的流体; 盛取时消耗 container.serving_ml. 例如默认苹果汁每次压榨 180ml, 玻璃瓶盛取需要 250ml, 所以玩家至少需要压榨两轮才可盛取一次

发酵桶示例 ​

yaml
id: "example_recipe"
display_name: "苹果酒"

inputs:
  - item_sources:
      - "minecraft-apple"
    amount: 3
  - item_sources:
      - "minecraft-sugar"
    amount: 1

fermentation_time_seconds: 300

fermentation:
  early_collect:
    min_progress_ratio: 0.5
  over_time_seconds: 600

permission: "emakicooking.recipe.apple_cider"

condition:
  type: all_of
  entries:
    - "%player_level% >= 3"
  on_pass:
    actions:
      - 'send_message text="<green>发酵成熟。</green>"'
  on_fail:
    actions:
      - 'send_message text="<yellow>发酵未满足额外条件。</yellow>"'
    block_output: false

result:
  success:
    outputs:
      - item_source: "minecraft-honey_bottle"
        amount: 1
    actions:
      - 'send_message text="<gold>发酵完成。</gold>"'
  early:
    outputs:
      - item_source: "minecraft-potion"
        amount: 1
    actions:
      - 'send_message text="<yellow>提前收取了半发酵苹果饮。</yellow>"'
  over:
    outputs:
      - item_source: "minecraft-honey_bottle"
        amount: 1
    actions:
      - 'send_message text="<gold>苹果酒已经继续转化为酸甜果醋。</gold>"'
字段说明
inputs必填. 发酵桶需要投入的多种材料; 每项使用同级 item_sources + matcher, 两者取 AND
inputs[].item_sources该输入项的物品来源列表; 与同级 matcher 取 AND
inputs[].amount该输入项需要的数量, 默认 1, 小于 1 按 1 处理
inputs[].slot_id持久化槽位身份. 当前配方中必须非空且唯一; 不要在改配方时把同一个身份再用到另一种材料上
inputs[].count_key数量聚合与消费身份. 可让多个稳定槽位共享同一聚合键, 但必须保持长期稳定
inputs[].matcher可选. 只写非物品源条件, 并与同级 item_sources 取 AND
fermentation_time_seconds必填. 正常完成发酵所需秒数, 小于 0 按 0 处理
fermentation.early_collect.min_progress_ratio允许提前收取的最低进度比例, 默认 1.0, 截断到 0~1. 仅当存在 result.early 分支时生效, 否则视为不可提前收取
fermentation.over_time_seconds正常完成后继续放置多久进入过度发酵, 默认 0. 仅当存在 result.over 分支时生效
result.early.outputs提前收取产物. 产出项使用单值 item_source
result.over.outputs过度发酵产物. 产出项使用单值 item_source
result.success.outputs正常完成产物. 产出项使用单值 item_source

发酵桶状态迁移与失败保护 ​

旧的发酵桶状态如果没有槽位身份, 恢复时会按当前配方逐个匹配已经保存的物品: 每个槽位必须只得到一个非空的 slot_id/count_key 候选, 而且解析出来的 slot_id 不能重复. 缺少物品, 配方不存在, 身份为空, 候选不唯一或槽位重复时, 迁移被拒绝, 状态不会写成规范格式的结果; 恢复, 交互和破坏这几条路径都会记录 warning 并停止继续处理. 已经有槽位身份, 但状态 schema (状态文件的结构) 还是旧版时, 只在成功恢复后才回写当前 schema. 以上都是源码与本地 JVM 测试覆盖的行为, 不等于在 Paper/Folia 上实机验证过

调试与验证 ​

  • 使用 /ecooking reload 重载配方
  • 使用 /ecooking inspect hand 检查手里物品能否被 Item Source 识别
  • 每个配方至少测试: 权限不足, 材料不足, 背包满, 正确分支, 失败分支, 服务器重启后的工位状态
  • 炒锅额外测试 success, undercooked, overcooked, invalid
  • 烤炉额外测试普通完成, 完美完成, 过烤
  • 榨汁机额外测试流体不足, 容器不匹配和不同流体混合
  • 发酵桶额外测试提前收取, 正常完成和过度发酵