简体中文
配方定义
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顶层字段
| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
id | string | 是 | — | — | 配方 id, 全局唯一. 缺失时该文件被拒绝加载 (station.recipe_missing_id) |
display_name | string | 否 | 取 id | MiniMessage 文本 | 界面上显示的名称 |
station_ids | list<string> | 否 | [] | 工作站 id 列表 | 声明这条配方属于哪些工作站. 留空表示对所有工作站开放. 比较时会先把值转成小写 |
tags | list<string> | 否 | [] | 字符串列表 | 仍然可以用在其他地方 (例如 API 查询), 但不再决定配方属于哪个工作站 |
permission | string | 否 | "" | 权限节点 | 使用这条配方需要的权限. 留空表示不限制 |
visible | boolean | 否 | true | true, false | 是否出现在配方目录里. false 表示完全不显示, 这种写法多用于配合 API 提交 |
duration_seconds | long | 否 | 0 | ≥0, 负数截断为 0 | 单次合成耗时 (秒). 填 0 或省略表示立即完成, 不进队列 |
cost | 段 | 否 | 不收费 | 见下表 | 每次合成额外收取的货币, 会按倍率乘算 |
materials | list | 是 | — | 非空列表 | 材料需求列表. 为空时整条配方被拒绝加载 (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: 1cost 货币成本
| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
cost | 段 | 否 | 不收费 | — | 缺 currency 子节时不报错, 直接按不收费处理, 也不写日志 |
cost.currency.type | string | 否 | "" | vault, excellent (excellenteconomy 等价) | 经济的来源方. 填了别的非空值会报 station.recipe_bad_currency, 并按不收费处理. 与 amount 同时为空时也按不收费处理 |
cost.currency.amount | long | 否 | 0 | >0 才收费 | 金额. 小于等于 0 会报 station.recipe_bad_cost_amount, 并按不收费处理 |
WARNING
type 和 amount 必须写在 cost.currency 子节里, 不能直接挂在 cost 下面.缺少 currency 子节时插件不报错, 也不打日志, 直接按不收费处理, 这样写出来的配置会不声不响地失效
插件先检查余额, 再扣材料, 扣款在扣完材料之后执行. 扣款失败会把材料按原额退回. 你取消队列条目时, 按工作站的 cancel_refund_rate 折算退还
materials 材料需求
materials 是一个列表, 每个元素是一条需求
| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
material_id | string | 否 | "<recipe_id>.material.<index>" | — | 这条材料在选择时的标识. 省略时会按索引自动生成, 并告警 station.recipe_derived_material_identity, 新配置应该显式写出来. 和配方里其他 material_id/requirement_id 撞名会拦下加载 (station.recipe_duplicate_material_identity) |
requirement_id | string | 否 | 取 material_id | — | 需求记录与队列凭据使用的标识 |
count_key | string | 否 | 取 material_id | — | 数量汇总与消费记录使用的标识 |
item_sources | list<string> | 否 | [] | 物品源 token (连字符简写) | 「任意其一」集合, 数量按集合内总数计算. 省略表示不限来源 |
item_source | string | 否 | — | 单个物品源 token | 旧写法. 和 item_sources 同时出现会被拒绝 (station.recipe_source_field_conflict). 只写它一个时可以读, 但会告警 station.recipe_legacy_item_source |
matcher | 段/object | 否 | — | 通用物品匹配器 | 只写组件, PDC, Lore 这类非物品源的条件, 和同一级的 item_sources 一起都要满足. 见物品匹配器 |
amount | long | 否 | 1 | >0 | 需要的总数. 小于等于 0 会报 station.recipe_bad_amount, 并跳过这一条需求 |
consume | boolean | 否 | true | true, false | false 表示只检查玩家有没有, 不真正扣除, 用在「需要工具拿在手上」这类需求 |
你把 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: trueWARNING
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.outputs | list | 是 | — | 非空列表 | 产物清单 |
result.outputs[].item_source | string | 是 | — | 单个物品源 token | 产物的来源, 单数形式. 写成列表或用非法 token 会报 station.recipe_bad_source, 并拒绝整条配方 |
result.outputs[].item_sources | list | 否 | — | 恰好 1 个元素 | 旧写法, 仍然可以读, 但会告警 station.recipe_legacy_item_source. 和 item_source 同时出现会被拒绝 |
result.outputs[].amount | long | 否 | 1 | >0 | 单次产物数量. 小于等于 0 会报 station.recipe_bad_amount, 并跳过这一项 |
result.actions | list<string> | 否 | [] | 动作管道行 | 结算后要执行的动作行. 目前会被解析, 但不执行 |
产物节点上不允许写 matcher: 写了会报 station.recipe_bad_source, 并拒绝整条配方
actions 阶段动作行
| 字段 | 类型 | 必填 | 默认值 | 取值范围 | 说明 |
|---|---|---|---|---|---|
actions.pre | list<string> | 否 | [] | 动作管道行 | 提交前要执行的动作行. 目前会被解析, 但不执行 |
actions.success | list<string> | 否 | [] | 动作管道行 | 成功时要执行的动作行. 目前会被解析, 但不执行 |
actions.failure | list<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 每次绘制目录页都会判断一次, 写得太复杂会拖慢界面