Skip to content

配方定义 ​

recipes/<配方>.yml 里每个字段怎么写, 材料怎么匹配与扣除, 产物怎么发放, 以及两种条件检查分别管什么

合成配方放在 plugins/EmakiStation/recipes/ 下, 一个文件就是一条配方. 文件名不参与识别, 配方 id 取文件内的 id 字段

配方的匹配方式是无序集合匹配: 只数材料的种类和总数, 不看放在哪一格, 也没有摆放形状的概念

完整示例 ​

yaml
id: "iron_ingot_block"
display_name: "<white>铁块压制</white>"

station_ids:
  - "blacksmith"

tags:
  - "blacksmith"

permission: ""
visible: true
duration_seconds: 30

cost:
  currency:
    type: "vault"
    amount: 0

materials:
  - material_id: "iron_ingot"
    requirement_id: "iron_ingot_input"
    count_key: "iron_ingot"
    item_sources:
      - "minecraft-iron_ingot"
    amount: 9
    consume: true

result:
  outputs:
    - item_source: "minecraft-iron_block"
      amount: 1
  actions: []

condition:
  invalid_as_failure: true

display_condition:
  invalid_as_failure: false

顶层字段 ​

字段类型必填默认值取值范围说明
idstring是——配方 id, 全局唯一. 缺失时该文件被拒绝加载 (station.recipe_missing_id)
display_namestring否取 idMiniMessage 文本界面上显示的名称
station_idslist<string>否[]工作站 id 列表声明这条配方属于哪些工作站. 留空表示对所有工作站开放. 比较时会先把值转成小写
tagslist<string>否[]字符串列表仍然可以用在其他地方 (例如 API 查询), 但不再决定配方属于哪个工作站
permissionstring否""权限节点使用这条配方需要的权限. 留空表示不限制
visibleboolean否truetrue, false是否出现在配方目录里. false 表示完全不显示, 这种写法多用于配合 API 提交
duration_secondslong否0≥0, 负数截断为 0单次合成耗时 (秒). 填 0 或省略表示立即完成, 不进队列
cost段否不收费见下表每次合成额外收取的货币, 会按倍率乘算
materialslist是—非空列表材料需求列表. 为空时整条配方被拒绝加载 (station.recipe_no_materials)
result段是—见下表产物段. 缺失或没有产物时整条配方被拒绝加载 (station.recipe_no_outputs)
actions段否全空见下表各阶段要执行的动作行. 目前会被解析, 但不执行
condition段否空条件见下表提交前的条件检查
display_condition段否空条件见下表解锁条件, 决定目录里是否显示为灰色

老版工作站文件里的 recipes 段 (include_tags / include_ids / exclude_ids) 已经废弃, 归属关系现在由配方侧的 station_ids 声明

你没有 permission 对应权限时, 配方不会出现在目录里, 而不是显示为灰色

yaml
# 示例: 一条对全部工作站开放, 无需权限, 立即完成的配方
id: "quick_charcoal"
display_name: "<gray>快速木炭</gray>"
duration_seconds: 0
materials:
  - item_sources:
      - "minecraft-oak_log"
    amount: 1
result:
  outputs:
    - item_source: "minecraft-charcoal"
      amount: 1

cost 货币成本 ​

字段类型必填默认值取值范围说明
cost段否不收费—缺 currency 子节时不报错, 直接按不收费处理, 也不写日志
cost.currency.typestring否""vault, excellent (excellenteconomy 等价)经济的来源方. 填了别的非空值会报 station.recipe_bad_currency, 并按不收费处理. 与 amount 同时为空时也按不收费处理
cost.currency.amountlong否0>0 才收费金额. 小于等于 0 会报 station.recipe_bad_cost_amount, 并按不收费处理

WARNING

type 和 amount 必须写在 cost.currency 子节里, 不能直接挂在 cost 下面.缺少 currency 子节时插件不报错, 也不打日志, 直接按不收费处理, 这样写出来的配置会不声不响地失效

插件先检查余额, 再扣材料, 扣款在扣完材料之后执行. 扣款失败会把材料按原额退回. 你取消队列条目时, 按工作站的 cancel_refund_rate 折算退还

materials 材料需求 ​

materials 是一个列表, 每个元素是一条需求

字段类型必填默认值取值范围说明
material_idstring否"<recipe_id>.material.<index>"—这条材料在选择时的标识. 省略时会按索引自动生成, 并告警 station.recipe_derived_material_identity, 新配置应该显式写出来. 和配方里其他 material_id/requirement_id 撞名会拦下加载 (station.recipe_duplicate_material_identity)
requirement_idstring否取 material_id—需求记录与队列凭据使用的标识
count_keystring否取 material_id—数量汇总与消费记录使用的标识
item_sourceslist<string>否[]物品源 token (连字符简写)「任意其一」集合, 数量按集合内总数计算. 省略表示不限来源
item_sourcestring否—单个物品源 token旧写法. 和 item_sources 同时出现会被拒绝 (station.recipe_source_field_conflict). 只写它一个时可以读, 但会告警 station.recipe_legacy_item_source
matcher段/object否—通用物品匹配器只写组件, PDC, Lore 这类非物品源的条件, 和同一级的 item_sources 一起都要满足. 见物品匹配器
amountlong否1>0需要的总数. 小于等于 0 会报 station.recipe_bad_amount, 并跳过这一条需求
consumeboolean否truetrue, falsefalse 表示只检查玩家有没有, 不真正扣除, 用在「需要工具拿在手上」这类需求

你把 item_sources 与 matcher 都省略时, 该条需求被跳过. 需要 64 个时, 30 个 coal 加 34 个 charcoal 也算满足

单个配方的材料种类数不受 GUI 槽位数限制; 超过 limits.warn_material_types 只记告警, 不拦截加载. 仓库通道按 long 直接扣, 背包通道受原版每格 64 上限约束

材料分配用的是确定性最大流: 你点提交时, 插件把背包里的每一堆物品与仓库里每个物品源的库存一起放进同一张分配图, 在全部需求之间寻找一套能同时满足的完整分配, 不依赖配方声明顺序. 约束更窄的 matcher 材料会优先参与建图, 但最终仍然以整体上能不能分配为准. 如果分配失败, 说明当前候选材料确实无法同时满足全部需求

通用物品匹配器 (matcher) ​

materials[] 的判定由 item_sources 与 matcher 两个同级并列字段合起来决定, 两者都要满足: item_sources 只写允许的物品源, matcher 只写组件, PDC, Lore 等非物品源条件. 两者都省略时该材料永远不会匹配上. 完整语法见物品匹配器

yaml
materials:
  # 铁锭, 且没被重命名过
  - item_sources:
      - minecraft-iron_ingot
    matcher:
      type: component
      component: custom_name
      operator: absent
    amount: 9
    consume: true

WARNING

matcher 内部不能写物品源条件. 写了 type: item_source / item_sources / source / sources 会被解析器拒绝并告警, 而且这个 matcher 永远不会匹配

声明了 matcher 的材料只能从背包供料

凡是声明了 matcher 的那一条材料, 只能从背包供料, 在仓库里找材料时会跳过它. 原因是仓库只按物品源统计库存, 看不到真实物品的组件, 无法证明组件条件成立

加载时会为这类材料打一条 storage_unreachable_material WARN (只有在这条配方挂在至少一个开启 channels.storage 的工作站上, 并且 storage.enabled: true 时才会出现). 看到这条提示就是这个原因, 不是配置写错了

需要从仓库供料的材料, 只写 item_sources, 不要写 matcher. 同一条配方里其他只写 item_sources 的材料仍然可以正常使用仓库通道

result 产物 ​

字段类型必填默认值取值范围说明
result.outputslist是—非空列表产物清单
result.outputs[].item_sourcestring是—单个物品源 token产物的来源, 单数形式. 写成列表或用非法 token 会报 station.recipe_bad_source, 并拒绝整条配方
result.outputs[].item_sourceslist否—恰好 1 个元素旧写法, 仍然可以读, 但会告警 station.recipe_legacy_item_source. 和 item_source 同时出现会被拒绝
result.outputs[].amountlong否1>0单次产物数量. 小于等于 0 会报 station.recipe_bad_amount, 并跳过这一项
result.actionslist<string>否[]动作管道行结算后要执行的动作行. 目前会被解析, 但不执行

产物节点上不允许写 matcher: 写了会报 station.recipe_bad_source, 并拒绝整条配方

actions 阶段动作行 ​

字段类型必填默认值取值范围说明
actions.prelist<string>否[]动作管道行提交前要执行的动作行. 目前会被解析, 但不执行
actions.successlist<string>否[]动作管道行成功时要执行的动作行. 目前会被解析, 但不执行
actions.failurelist<string>否[]动作管道行失败时要执行的动作行. 目前会被解析, 但不执行

这四份动作列表 (含 result.actions) 会保存在配方对象上, API 可以读到; 但运行时目前没有任何执行入口, 你写进去不会有效果

两种条件门的区别 ​

段类型必填默认值取值范围说明
condition段否空条件 (恒通过)见条件系统决定「能不能做」. 在提交校验时判断, 不满足会返回 station.recipe_condition_failed
display_condition段否空条件 (恒解锁)同上决定「解锁了没有」. 每次绘制目录页时判断, 不满足时该条目显示为灰色占位, 点了没有反应
段决定什么不满足时的表现
condition能不能做配方照常显示, 点了会被拒绝
display_condition解锁没有配方显示为灰色占位, 点击无反应

两段的 invalid_as_failure 含义与其他模块一致: condition 与 display_condition 在源码里的默认值都是 true (示例文件把 display_condition 显式写成 false, 这样条件写错时更倾向「视为已解锁」, 而不是把配方锁死). 这两段只判断条件组本身; on_pass.actions / on_fail.actions / on_fail.message 目前都不执行

用灰色占位而不是隐藏, 是让你知道后面还有内容. 想彻底隐藏请用 visible: false 或 permission

display_condition 每次绘制目录页都会判断一次, 写得太复杂会拖慢界面