工位系统
Cooking 工位承载配方交互、进度状态、材料缓存、燃料、流体、阶段数据和结果产出。一个工位可以对应方块、GUI 或交互实体;排查时先确认方块来源,再看工位状态和配方匹配。
工位类型
| 工位 | 主要状态 | 适合玩法 |
|---|---|---|
| 砧板 | 当前输入、累计数量、切割次数 | 切菜、分割、预处理。 |
| 研磨机 | 当前输入、剩余时间 | 磨粉、研磨药材、压碎矿物。 |
| 蒸锅 | 当前输入、燃料、水分、蒸汽进度 | 蒸鱼、蒸饭、熟制半成品。 |
| 炒锅 | 材料列表、火候、翻炒次数、错误次数 | 需要操作节奏和判定的料理。 |
| 烤炉 | 输入、燃烧时间、火力、烘烤阶段 | 需要控温和阶段判定的烘焙。 |
| 榨汁机 | 输入、压榨次数、流体 ID、流体容量 | 果汁、饮品、按容器分装。 |
| 发酵桶 | 多输入、发酵进度、是否完成、阶段 | 酒、醋、发酵半成品。 |
配置顺序
- 先在
config.yml里确认工位方块来源和交互方式。 - 再到
recipes/<station>/写对应工位配方。 - 如果工位有 GUI,再调整
gui/*.yml。 - 使用
/ec inspect hand检查材料、工具、容器和方块来源。 - 使用
/ec debug排查工位监听、展示实体和持久化状态。
通用配置字段
| 字段 | 类型 | 说明 |
|---|---|---|
block_item_sources | list | 绑定的方块来源,推荐使用 minecraft-oak_log 这类短横线格式。不支持 matcher,见下方说明。 |
interactions | object | 每个操作对应的点击方式。 |
drop_result | boolean | 结果是否直接掉落到世界。 |
only_recipe_items | boolean | 是否只允许可匹配配方的物品进入输入。 |
disabled_worlds | list | 该工位的禁用世界列表,默认空列表。命中后插件不接管该工位事件,保留原版方块行为。 |
工具与容器的通用物品匹配器
工位的工具、锅铲和容器识别写在各自的子节点下,子节点内是标准的 item_sources + matcher 两个同级字段:
| 子节点 | 作用 | 旧的扁平键(仍兼容读取) |
|---|---|---|
stations.chopping_board.tool | 砧板工具 | tool_matcher / tool_item_sources |
stations.wok.spatula | 炒锅锅铲 | spatula_matcher / spatula_item_sources |
stations.juicer.container | 榨汁机容器 | container_matcher / container_item_sources |
TIP
子节点里 item_sources 与 matcher 取 AND:item_sources 只写允许的物品源,matcher 只写组件 / PDC / Lore 等条件。两者都省略时该角色永不命中。
matcher 内部不能写物品源条件(type: item_source / item_sources / source / sources 会被拒绝并告警)。
旧的扁平前缀键仍被加载期兼容读取,不会失效,但推荐迁移到子节点写法。
stations:
chopping_board:
block_item_sources:
- minecraft-oak_log
# 既是铁斧,又附了效率:物品源写 item_sources,组件条件写 matcher
tool:
item_sources:
- minecraft-iron_axe
matcher:
type: component
component: enchantments
path: efficiency
operator: '>='
value: 1DANGER
block_item_sources 不支持 matcher。 它回答的是"哪个方块算这个工位",属于方块侧识别,不是物品输入匹配。在那里写 matcher 不会生效。
同理,配方产出侧(result.<branch>.outputs)也不支持。详见 只有输入匹配点支持 matcher。
完整语法见 物品匹配器。
世界黑名单
工位交互可以按世界禁用,分全局和按工位两层,两层都默认空列表(不禁用任何世界):
# 全局:对所有工位生效
station:
disabled_worlds:
- world_nether
stations:
chopping_board:
# 按工位:只对该工位生效
disabled_worlds:
- world_the_end命中黑名单时插件不接管相关事件,方块保持原版行为,因此可以用它在资源世界或副本世界里保留原版交互。
状态存储与 IO 优化
EmakiCooking 会按实际锚点方块选择工位状态后端:如果方块状态实现 Bukkit TileState,工位状态优先写入该方块实体的 PersistentDataContainer(BLOCK_PDC);如果只是普通方块,则回退到 data/stations/ 的 YAML 文件(YAML_FALLBACK)。
这对服务器 IO 进程性能有直接帮助:
- 大量工位使用方块实体后,状态随区块/方块实体保存链路持久化,避免每个工位频繁生成和改写独立 YAML 文件。
data/stations/index/<world>.idx只保存坐标、类型、来源和后端,用于按区块快速定位工位,减少重启或 ChunkLoad 时的全目录扫描。- 旧 YAML 状态在成功迁入方块实体 PDC 后会移到
data/stations-legacy-backup/,便于回退排查而不会继续作为热路径文件。 - 非方块实体仍保持 YAML fallback;因此性能敏感、工位数量多的服务器,建议优先把工位锚点配置成方块实体方块。
PDC 键名
工位状态写入方块实体 PDC 时使用下列键,命名空间为插件名 emakicooking(即完整键形如 emakicooking:station_state)。用第三方 NBT 工具排查时按这些键读取:
| 键 | 说明 |
|---|---|
station_state | 序列化后的工位状态主体。 |
station_type | 工位类型。 |
station_source | 工位方块来源。 |
station_saved_at_ms | 最后一次落盘的时间戳(毫秒)。 |
station_state_version | 状态版本号,用于并发与陈旧写入判定。 |
station_tombstone | 墓碑标记,表示该位置的工位已移除。 |
物品侧另有一个 PDC 键 cooking_history:以逗号分隔记录该物品经过的配方 ID,蒸锅链式配方的 requires_previous_step 就是靠它判定前置步骤。
排查命令:
/ec inspect block:查看准星目标方块的block_state、tile_state、storage_backend、pdc_state、indexed和legacy_yaml。/ec station reindex:重建工位坐标索引,扫描 legacy YAML 与当前已加载区块中的 PDC 工位。
Minecraft 方块实体列表(适合做工位锚点)
下面按 Paper/Bukkit TileState 可识别的常见原版方块实体整理;具体可用性仍以服务器版本和目标方块 tile_state=yes 为准。同一类的木种、颜色或变体合并展示。
| 类型 | 常见方块 |
|---|---|
| 容器 | 箱子、陷阱箱、木桶、潜影盒、末影箱、漏斗、发射器、投掷器、唱片机、讲台、錾制书架 |
| 处理/制作 | 熔炉、烟熏炉、高炉、酿造台、营火、灵魂营火、合成器 |
| 展示/装饰 | 旗帜、床、告示牌、悬挂告示牌、玩家头颅、饰纹陶罐、蜂巢、蜂箱 |
| 红石/功能 | 比较器、日照探测器、命令方块、结构方块、拼图方块、移动活塞 |
| 世界/特殊 | 信标、附魔台、钟、潮涌核心、刷怪笼、试炼刷怪笼、宝库、末地折跃门 |
| 幽匿/考古 | 幽匿感测体、校频幽匿感测体、幽匿催发体、幽匿尖啸体、可疑沙子、可疑砂砾 |
| 新版本 API 暴露 | 铜傀儡雕像、货架、测试方块、测试实例方块 |
砧板示例
stations:
chopping_board:
block_item_sources:
- minecraft-oak_log
only_recipe_items: true
interactions:
place_input: shift_left_click
process: shift_left_click
return_input: right_click
drop_result: true
interaction_delay_ms: 1000
tool:
item_sources:
- minecraft-iron_axe砧板的输入交互会一次性接收玩家主手整叠物品,并在运行态累计数量;物品展示实体仍只显示 1 个输入物品。配方里的 input.amount 表示每轮完成切割需要并消耗的数量,累计数量不足时不能开始切割。若配方设置 cuts_required: 1,玩家可以对已放入的一批食材连续切割,无需每次重新放入单个食材。
炒锅示例
stations:
wok:
block_item_sources:
- minecraft-iron_block
interactions:
add_ingredient: shift_left_click
stir: shift_left_click
serve: shift_left_click
return_ingredient: shift_left_click
inspect: shift_right_click
drop_result: true
need_bowl: true
stir_delay_ms: 5000
timeout_ms: 30000
spatula:
item_sources:
- minecraft-iron_shovel
heat_levels:
- item_sources:
- minecraft-campfire
level: 1
- item_sources:
- minecraft-magma_block
level: 2
- item_sources:
- minecraft-lava
level: 3炒锅热度参数
| 字段 | 说明 |
|---|---|
need_bowl | 是否要求手持碗才能出锅。 |
stir_delay_ms | 两次翻炒之间的最小间隔。 |
timeout_ms | 锅状态超时后按过火处理。 |
heat_levels[].item_sources | 命中的下方方块来源。 |
heat_levels[].level | 对应火候等级。 |
研磨机示例
stations:
grinder:
block_item_sources:
- minecraft-grindstone
interactions:
start: shift_left_click
drop_result: true
check_delay_ticks: 20
actions:
start:
- 'play_sound sound=block.grindstone.use volume=0.7 pitch=1.0'
running:
- 'spawn_particle particle=SMOKE count=2 offset_x=0.15 offset_y=0.15 offset_z=0.15'
complete:
- 'play_sound sound=entity.experience_orb.pickup volume=0.8 pitch=1.2'| 字段 | 说明 |
|---|---|
check_delay_ticks | 后台检查周期(tick)。研磨机放入材料后自动开始,按此间隔检查进度。 |
interactions.start | 手动启动研磨的交互方式。 |
研磨机是最简单的工位——放入材料后自动开始研磨,达到配方时间后产出结果。不需要燃料、水分或手动翻炒。
蒸锅示例
stations:
steamer:
block_item_sources:
- minecraft-barrel
interactions:
open: shift_right_click
fuel: right_click
moisture: right_click
drop_result: true
heat_item_sources:
- minecraft-furnace
- minecraft-smoker
- minecraft-blast_furnace
ignite_heat_source: true
fuels:
- item_sources: ["minecraft-stick"]
duration_seconds: 5
- item_sources: ["minecraft-coal"]
duration_seconds: 80
moisture_rules:
# input 子节点判定投入的那一枚;规则根部的 item_sources 是返还的空容器,属产出侧
- input:
item_sources: ["minecraft-water_bucket"]
item_sources:
- minecraft-bucket
moisture: 120
- input:
item_sources: ["minecraft-potion"]
item_sources:
- minecraft-glass_bottle
moisture: 40
reset_progress_when_steam_empty: true
steam_production_efficiency: 10
steam_conversion_efficiency: 1
steam_consumption_efficiency: 1| 字段 | 说明 |
|---|---|
heat_item_sources | 蒸锅下方热源方块来源。支持对象写法指定 lit_item_sources 作为点亮替换。 |
ignite_heat_source | 加燃料后是否点亮下方热源外观。 |
fuels[].item_sources | 燃料允许的物品来源。省略表示不限来源。 |
fuels[].matcher | 燃料的通用物品匹配器,只写组件 / PDC / Lore 等非物品源条件,与同级 item_sources 取 AND。两者都省略时该条目永不命中。见 物品匹配器。 |
fuels[].duration_seconds | 投入燃料后增加的燃烧时间。 |
moisture_rules[].input.item_sources | 加水输入允许的物品来源。 |
moisture_rules[].input.matcher | 加水输入的通用物品匹配器,与同级 input.item_sources 取 AND。两者都省略时该规则永不命中。旧的扁平 input_matcher / input_item_sources 仍被兼容读取。 |
moisture_rules[].item_sources | 加水后返还的容器物品来源。这是产出侧构造字段,不是判定位。 |
moisture_rules[].moisture | 加水后增加的水分值。 |
reset_progress_when_steam_empty | 蒸汽耗尽时是否重置全部进度。 |
steam_production_efficiency | 每个周期最多将多少 moisture 转成 steam。 |
steam_conversion_efficiency | 每次 steam 消耗换算成多少进度。 |
steam_consumption_efficiency | 每个周期的基础 steam 消耗量。 |
蒸锅的运行逻辑:燃料提供燃烧时间 → 燃烧时将 moisture 转化为 steam → steam 推进蒸制进度。三个 efficiency 参数控制转化速率。水分和燃料都需要玩家手动补充。
烤炉示例
stations:
oven:
block_item_sources:
- minecraft-smoker
interactions:
open: shift_right_click
fuel: shift_left_click
inspect: shift_left_click
drop_result: true
heat:
min: 20
max: 80
decay_per_second: 5
fuels:
- item_sources: ["minecraft-stick"]
duration_seconds: 5
heat: 10
- item_sources: ["minecraft-coal"]
duration_seconds: 80
heat: 35| 字段 | 说明 |
|---|---|
heat.min / heat.max | 火力处于该区间时才推进烤制计时。 |
heat.decay_per_second | 每秒自然衰减的火力。 |
fuels[].duration_seconds | 投入燃料后增加的燃烧时间。 |
fuels[].heat | 投入燃料后增加的火力。 |
fuels[].item_sources | 燃料允许的物品来源。省略表示不限来源。 |
fuels[].matcher | 燃料的通用物品匹配器,只写组件 / PDC / Lore 等非物品源条件,与同级 item_sources 取 AND。两者都省略时该条目永不命中。见 物品匹配器。 |
fuel 与 inspect 同绑时靠手持物区分
上面的默认配置把 fuel 和 inspect 都绑到 shift_left_click,两者靠手持物互斥消歧,不会冲突:
- 手持物命中某条
fuels[]条目 → 走加燃料。 - 手持物为空 → 走查看信息。
- 手持物既不是燃料也不为空 → 两个分支都不进入。
所以查看烤炉信息前要先空手。
烤炉的运行状态会记录当前火力、剩余燃烧时间、累计烘烤时间和烘烤阶段。火力过低或过高都会暂停正常烘烤,配方可根据完美火力占比和过烤时间决定最终产物。
榨汁机示例
stations:
juicer:
block_item_sources:
- minecraft-cauldron
interactions:
open: shift_right_click
process: shift_left_click
inspect: right_click
serve: shift_left_click
drop_result: true
only_recipe_items: true
require_container: true
max_fluid_ml: 1000
default_serving_ml: 250
container:
item_sources:
- minecraft-glass_bottle| 字段 | 说明 |
|---|---|
require_container | 是否要求容器才能盛取结果。 |
max_fluid_ml | 榨汁机内部默认最大流体容量。 |
default_serving_ml | 默认每次容器盛取需要的流体量。 |
container.item_sources | 可作为容器的物品来源。 |
container.matcher | 容器的通用物品匹配器,只写非物品源条件,与同级 item_sources 取 AND。两者都省略时没有物品能当容器。旧的扁平 container_matcher 仍被兼容读取。 |
serve 与 process 同绑时按盛取优先级判定
上面的默认配置把 serve 和 process 都绑到 shift_left_click。判定顺序是先看 serve 能不能优先:工位内有流体,且满足「不要求容器」或「手持有效盛取容器」时,盛取优先;两者都不满足时,只有玩家手上也没有可压榨材料才退回盛取,否则走压榨。
因此手持容器时是盛取,手持食材时是压榨,同一个键位不会互相挡住。
榨汁机会把压榨产物保存为“流体 ID + 容量”。同一台榨汁机同一时间只应保存一种流体,避免不同饮品混入。容量达到容器所需 serving_ml 后才可盛取。
发酵桶示例
stations:
fermentation_barrel:
block_item_sources:
- minecraft-barrel
interactions:
open: shift_right_click
start: shift_left_click
inspect: right_click
serve: shift_left_click
drop_result: true
pause_when_open: true
only_recipe_items: true| 字段 | 说明 |
|---|---|
pause_when_open | GUI 打开时是否暂停发酵进度。 |
only_recipe_items | 是否只允许配方材料进入输入。 |
interactions.serve 是死键
发酵桶只分派 open、start、inspect 三个操作,serve 不参与判定——默认 config.yml 里保留了这个键,但改它不产生任何效果。
收取动作由 start 绑定按当前状态分派:
- 已完成:收取正常完成产物。
- 正在发酵且处于提前收取阶段:收取提前产物。
- 其他情况:开始发酵。
也就是说想改收取键位,要改的是 interactions.start。
发酵桶适合多输入长周期配方。配方可以定义提前收取比例、正常完成结果和过度发酵结果。
展示实体
Cooking 支持把工位输入、半成品或结果以展示实体形式呈现在世界中。展示实体由 display_entities 和 display_adjustments 控制。
display_entities:
backend: auto
view_distance_blocks: 48
refresh_interval_ticks: 20| 字段 | 说明 |
|---|---|
backend | 展示后端,通常可使用 auto、packet_events 或 bukkit。 |
view_distance_blocks | 玩家可见距离。 |
refresh_interval_ticks | 展示刷新间隔。 |
display_adjustments 可按工位调整偏移、旋转和缩放:display_adjustments.defaults.item / defaults.block 是物品型与方块型展示的全局默认值,display_adjustments.station_defaults.<工位> 按工位覆盖。配置自定义方块工位时,建议先使用默认值确认展示正常,再微调高度和旋转。
display_entities.text 控制工位运行态文本展示实体(结果 / 过程 / 下一步引导):
| 字段 | 说明 |
|---|---|
text.enabled | 是否启用文本展示实体。 |
text.billboard | 朝向模式:fixed、vertical、horizontal、center。 |
text.line_width | 文本最大行宽(像素)。 |
text.background | 背景色 ARGB 整数;0 表示完全透明。 |
text.shadow | 是否渲染文字阴影。 |
text.see_through | 是否穿墙可见。 |
text.defaults.offset / text.defaults.scale | 文本相对工位方块的偏移与缩放。 |
text.stations.<工位>.enabled | 按工位开关文本展示;省略时默认启用。 |
物品展示调整
item_adjustments/ 目录用于给特定物品在特定工位上单独设置展示参数。文件名不参与匹配,实际以文件内 item_sources 解析出的来源为准;同一来源只保留最后加载的一份。
item_sources:
- "minecraft-carrot"
stations:
chopping_board:
offset:
x: 0.54
y: 1.01
z: 0.52
rotation:
x: 90.0
y: 0.0
z: 24.0
scale:
x: 0.72
y: 0.72
z: 0.72
wok:
offset:
x: 0.47
y: 1.0
z: 0.45
rotation:
x: 88.0
y: 12.0
z: "-28.0-18.0"
scale:
x: 0.56
y: 0.56
z: 0.56| 字段 | 说明 |
|---|---|
item_sources | 匹配的物品来源。解析出的来源简写作为该调整的键。 |
adjustment | 可选。适用于所有工位的共享调整;省略该段时,会尝试直接从文件根读取 offset / rotation / scale。 |
stations.<工位> | 按工位覆盖的调整,工位键使用文件夹名。 |
offset.x/y/z | 相对工位方块的位置偏移。 |
rotation.x/y/z | 旋转角度。支持 "-28.0-18.0" 这类范围写法表示随机取值。 |
scale.x/y/z | 缩放比例。 |
若文件既没有共享调整也没有任何可识别的工位调整,该文件会被跳过。
炒锅动画与烫伤
炒锅可配置翻炒动画和过热惩罚,用于强化操作节奏:
stir_animation:
enabled: true
duration_ticks: 20
height: 1.0
rotation_axis: x
rotation_degrees: 360.0
scald_damage:
enabled: true
value: 2| 字段 | 说明 |
|---|---|
stir_animation.duration_ticks | 翻炒动画持续时间。 |
stir_animation.height | 动画抛起高度。 |
stir_animation.rotation_axis | 旋转轴。 |
stir_animation.rotation_degrees | 旋转角度。 |
scald_damage.enabled | 是否启用烫伤。 |
scald_damage.value | 烫伤伤害值。 |
如果炒锅用于轻休闲玩法,可以关闭烫伤或降低伤害;如果用于节奏挑战,可以提高错误惩罚并配合失败产物。
状态生命周期
- 玩家右键或按配置点击工位。
- 模块检查权限、冷却、工位是否被占用以及手持物是否满足交互要求。
- 打开 GUI 或进入交互状态。
- 玩家放入材料、投入燃料、注入水分、压榨或开始发酵。
- 工位记录进度,例如切割次数、研磨剩余时间、蒸汽、火候、流体容量或发酵阶段。
- 达成条件后消耗输入并生成结果;部分工位可先进入“完成待领取”状态。
- 执行动作并清理临时状态,必要时保留剩余流体、剩余燃料或剩余进度。
多人和重启处理
- 同一工位应避免被多个玩家同时修改同一份材料缓存。
- 如果允许协作,需要明确谁获得产物、谁承担材料消耗。
- 工位状态优先随方块实体 PDC 持久化,普通方块使用
data/stations/YAML fallback。 data/stations/index/<world>.idx记录坐标与后端,ChunkLoad 时按索引恢复容器内容、进度和展示状态,ChunkUnload 时卸载运行态缓存。- 若直接删除或替换方块实体,索引可能仍指向旧 PDC 工位;用
/ec inspect block判断 backend/PDC,再用/ec station reindex重建索引。 - 对榨汁机、烤炉和发酵桶,额外确认流体容量、烘烤阶段和发酵进度能正确序列化。
工位权限
各工位的操作权限节点:
| 权限 | 说明 |
|---|---|
emakicooking.station.chopping_board.use | 使用砧板。 |
emakicooking.station.chopping_board.cut | 执行砧板切割。 |
emakicooking.station.wok.use | 使用炒锅。 |
emakicooking.station.wok.stir | 翻炒。 |
emakicooking.station.wok.serve | 出锅。 |
emakicooking.station.grinder.use | 使用研磨机。 |
emakicooking.station.steamer.use | 使用蒸锅。 |
emakicooking.station.steamer.fuel | 向蒸锅投入燃料。 |
emakicooking.station.steamer.moisture | 向蒸锅注入水分。 |
emakicooking.station.oven.use | 使用烤炉。 |
emakicooking.station.oven.fuel | 向烤炉投入燃料。 |
emakicooking.station.juicer.use | 使用榨汁机。 |
emakicooking.station.juicer.press | 执行压榨操作。 |
emakicooking.station.juicer.collect | 盛取榨汁结果。 |
emakicooking.station.fermentation_barrel.use | 使用发酵桶。 |
emakicooking.station.fermentation_barrel.start | 开始发酵。 |
emakicooking.station.fermentation_barrel.collect | 收取发酵结果。 |
NOTE
以上工位权限默认全部为 true。配方还可以通过自身的 permission 字段单独限制使用者。
GUI 覆盖范围
| 工位 | 是否有 GUI |
|---|---|
| 蒸锅 | 有 |
| 烤炉 | 有 |
| 榨汁机 | 有 |
| 发酵桶 | 有 |
| 砧板 | 无(直接交互) |
| 研磨机 | 无(直接交互) |
| 炒锅 | 无(直接交互) |
GUI 配置结构
四个有 GUI 的工位各对应 gui/ 下的一个文件:gui/steamer.yml、gui/oven.yml、gui/juicer.yml、gui/fermentation_barrel.yml。
gui_type: CHEST
title: "<dark_gray>蒸锅"
rows: 1
slots:
ingredient_slots:
slots:
- 0
- 1
- 2
- 3
- 4
type: "ingredient"| 字段 | 说明 |
|---|---|
gui_type | 容器类型,当前四个文件都是 CHEST。 |
title | 界面标题,支持 MiniMessage。 |
rows | 界面行数,读取后截断到 1~6。默认值:蒸锅 / 烤炉 / 榨汁机为 1,发酵桶为 3。 |
slots.<组名>.slots | 该组占用的槽位下标列表。超出 rows × 9 的下标被忽略。 |
slots.<组名>.type | 槽位组类型。当前只实现了 ingredient:解析器遍历 slots 下每个子节,type 非空且不等于 ingredient 的组会被整组跳过(留空视为 ingredient)。 |
组名本身不参与判定,只用于分组可读性。若 slots 为空或全部组都被跳过,回退为占用前 N 格(蒸锅 / 烤炉 / 榨汁机 N=5,发酵桶 N=7)。