Skip to content

Stations

Cooking stations store interaction state, input items, progress, fuel/fluid/stage data, and output handling. A station usually maps to a world block, a GUI, or a custom interaction entity.

Station types

StationMain stateBest for
Chopping BoardInput item, accumulated amount, and cut countSlicing and preprocessing.
GrinderInput item and remaining timeGrinding powders or herbs.
SteamerInput item, fuel, moisture, and steam progressSteamed food and cooked intermediates.
WokIngredients, heat, stir count, mistakesTiming-based cooking gameplay.
OvenInput, burn time, heat, and baking stageBaking with heat control and stage resolution.
JuicerInput, press count, fluid id, and fluid amountJuice and drinks bottled by serving capacity.
Fermentation BarrelMultiple inputs, progress, completion flag, and stageWine, vinegar, and fermented intermediates.

Common config fields

FieldDescription
block_item_sourcesBlock sources bound to the station. Prefer dash-style ids such as minecraft-oak_log. Does not support matcher, see below.
interactionsClick mapping for each station operation.
drop_resultWhether results are dropped directly in the world.
only_recipe_itemsWhether only items that can match a recipe may enter the input.
disabled_worldsPer-station list of disabled worlds, empty by default. In a listed world the plugin does not take over that station's events and vanilla block behavior is preserved.

Matchers for tools and containers

Tool, spatula, and container detection each live in their own child node holding a standard item_sources + matcher pair:

NodeFieldsLegacy flat key still read
tooltool.item_sources, tool.matchertool_matcher, tool_item_sources
spatulaspatula.item_sources, spatula.matcherspatula_matcher, spatula_item_sources
containercontainer.item_sources, container.matchercontainer_matcher, container_item_sources

TIP

item_sources and matcher are siblings and both must hold (AND). item_sources carries only the allowed item sources; matcher carries only non-item-source conditions such as component, PDC, or lore. Either may be omitted; omitting both is what makes the role never match.

Item source conditions must not be written inside matchertype: item_source and its aliases are rejected at load time with a warning and never match.

The old flat keys (tool_matcher, spatula_matcher, container_matcher) are still read for compatibility, so existing configs keep working. Write the nested form in new configs. Recipe inputs follow exactly the same item_sources + matcher rule, see Recipes.

yaml
stations:
  chopping_board:
    block_item_sources:
      - minecraft-oak_log
    # Two sibling fields: an iron axe that also has Efficiency
    tool:
      item_sources:
        - minecraft-iron_axe
      matcher:
        type: component
        component: enchantments
        path: efficiency
        operator: '>='
        value: 1

DANGER

block_item_sources does not support matchers. It answers which block counts as the station — block-side detection, not item input matching. A matcher there has no effect.

The same applies to the recipe output side (result.<branch>.outputs). See Only input-matching fields support matcher.

Full syntax is in Item Matcher.

World blacklist

Station interaction can be disabled per world at two levels, both defaulting to an empty list (nothing disabled):

yaml
# Global: applies to every station
station:
  disabled_worlds:
    - world_nether

stations:
  chopping_board:
    # Per station: applies to this station only
    disabled_worlds:
      - world_the_end

In a blacklisted world the plugin does not take over the related events and the block keeps vanilla behavior, so you can use this to preserve vanilla interaction in resource or instance worlds.

State storage and IO optimization

EmakiCooking chooses the station-state backend from the actual anchor block. If the block state implements Bukkit TileState, station state is written to that block entity's PersistentDataContainer (BLOCK_PDC). If the anchor is a normal block, the module falls back to YAML files under data/stations/ (YAML_FALLBACK).

This can further improve server IO-process performance:

  • When many stations use block entities, state follows the chunk/block-entity save path instead of creating and rewriting one standalone YAML file per station.
  • data/stations/index/<world>.idx stores only coordinates, station type, source, and backend so ChunkLoad recovery can locate stations by chunk without a full directory scan.
  • Legacy YAML states are moved to data/stations-legacy-backup/ after a successful migration to block-entity PDC, keeping them available for investigation without leaving them on the hot path.
  • Non-block-entity anchors still use YAML fallback; for large or performance-sensitive servers, prefer block-entity blocks as station anchors.

PDC keys

Station state written to block-entity PDC uses the keys below, under the plugin-name namespace emakicooking (so the full key looks like emakicooking:station_state). Read these keys when investigating with third-party NBT tools:

KeyDescription
station_stateThe serialized station state itself.
station_typeStation type.
station_sourceStation block source.
station_saved_at_msTimestamp of the last write, in milliseconds.
station_state_versionState version used for concurrency and stale-write checks.
station_tombstoneTombstone marker meaning the station at this position was removed.

Items carry one more PDC key, cooking_history: a comma-separated list of the recipe ids an item has passed through. Chained steamer recipes rely on it to resolve requires_previous_step.

Diagnostics:

  • /ec inspect block: checks the targeted block's block_state, tile_state, storage_backend, pdc_state, indexed, and legacy_yaml values.
  • /ec station reindex: rebuilds the station coordinate index from legacy YAML and currently loaded PDC stations.

Minecraft block entity list (good station anchors)

The list below groups common vanilla block entities that Paper/Bukkit can expose as TileState. Availability still depends on the server version and the inspected block showing tile_state=yes. Wood, color, and variant families are merged.

TypeCommon blocks
ContainersChest, trapped chest, barrel, shulker box, ender chest, hopper, dispenser, dropper, jukebox, lectern, chiseled bookshelf
Processing / craftingFurnace, smoker, blast furnace, brewing stand, campfire, soul campfire, crafter
Display / decorationBanner, bed, sign, hanging sign, player head, decorated pot, bee nest, beehive
Redstone / utilityComparator, daylight detector, command block, structure block, jigsaw block, moving piston
World / specialBeacon, enchanting table, bell, conduit, spawner, trial spawner, vault, end gateway
Sculk / archaeologySculk sensor, calibrated sculk sensor, sculk catalyst, sculk shrieker, suspicious sand, suspicious gravel
Newer API-exposed blocksCopper golem statue, shelf, test block, test instance block

Chopping board example

yaml
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

The chopping board input interaction takes the whole main-hand stack and accumulates it in station state; the item display entity still shows only one input item. In recipes, input.amount is the amount required and consumed for each completed cut cycle, and cutting cannot start until the accumulated amount is high enough. With cuts_required: 1, players can continuously process an already placed batch without re-placing one ingredient at a time.

Wok example

yaml
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
FieldDescription
need_bowlWhether a bowl is required to serve.
stir_delay_msMinimum delay between two stirs.
timeout_msTreat the wok as overcooked after this timeout.
heat_levels[].item_sourcesBlock sources below the wok.
heat_levels[].levelHeat level produced by those blocks.

Grinder example

yaml
stations:
  grinder:
    block_item_sources:
      - minecraft-grindstone
    interactions:
      start: shift_left_click
    drop_result: true
    check_delay_ticks: 20
FieldDescription
check_delay_ticksBackground check interval (ticks). The grinder starts automatically after input and checks progress at this interval.
interactions.startInteraction type to manually start grinding.

The grinder is the simplest station — place an input item, it starts grinding automatically, and produces a result when the recipe time is reached. No fuel, moisture, or manual stirring required.

Steamer example

yaml
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,matcher} decides the item put in; the item_sources at the
      # rule root is the empty container handed back, a construction field, not a match
      - 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
FieldDescription
heat_item_sourcesBlock sources below the steamer that act as heat sources. Supports object format with lit_item_sources for lit-state replacement.
ignite_heat_sourceWhether adding fuel lights the heat source block appearance.
fuels[].item_sourcesAllowed fuel item sources.
fuels[].matcherNon-item-source conditions for the fuel (component, PDC, lore). ANDed with the sibling item_sources; a fuel entry never matches only when both are omitted. See Item Matcher.
fuels[].duration_secondsBurn time added by the fuel.
moisture_rules[].input.item_sourcesAllowed moisture input item sources.
moisture_rules[].input.matcherNon-item-source conditions for the moisture input, ANDed with input.item_sources. The legacy flat input_matcher / input_item_sources keys are still read for compatibility. See Item Matcher.
moisture_rules[].item_sourcesContainer item returned after adding moisture. This one is a construction field, not a matching position.
moisture_rules[].moistureMoisture value added.
reset_progress_when_steam_emptyWhether to reset all progress when steam runs out.
steam_production_efficiencyMax moisture converted to steam per cycle.
steam_conversion_efficiencyProgress gained per steam consumed.
steam_consumption_efficiencyBase steam consumed per cycle.

Steamer logic: fuel provides burn time → burning converts moisture into steam → steam advances steaming progress. The three efficiency parameters control conversion rates. Both moisture and fuel must be manually replenished by the player.

Oven example

yaml
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
FieldDescription
heat.min / heat.maxBaking only advances while heat stays in this range.
heat.decay_per_secondNatural heat decay per second.
fuels[].duration_secondsBurn time added by the fuel.
fuels[].heatHeat added by the fuel.
fuels[].item_sourcesAllowed fuel item sources.
fuels[].matcherNon-item-source conditions for the fuel, ANDed with the sibling item_sources. See Item Matcher.

fuel and inspect share a binding and are told apart by the held item

The default config above binds both fuel and inspect to shift_left_click. They do not clash because the held item is mutually exclusive:

  • The held item matches one of the fuels[] entries → add fuel.
  • The hand is empty → show info.
  • The held item is neither fuel nor empty → neither branch runs.

So empty your hand before inspecting the oven.

The oven runtime records current heat, remaining burn time, accumulated baking time, and baking stage. Heat that is too low or too high pauses normal baking. Recipes can use perfect heat ratio and overbake time to resolve the final result.

Juicer example

yaml
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
FieldDescription
require_containerWhether a container is required to serve.
max_fluid_mlDefault internal fluid capacity in milliliters.
default_serving_mlDefault fluid amount consumed per serving.
container.item_sourcesAllowed container item sources.
container.matcherNon-item-source conditions for the container, ANDed with container.item_sources. Omit both and no item can act as a container. The legacy flat container_matcher key is still read for compatibility.

serve and process share a binding and are resolved by serving precedence

The default config above binds both serve and process to shift_left_click. The order of resolution checks whether serve takes precedence first: it does when the station holds fluid and either containers are not required or the player holds a valid serving container. When neither holds, serving is only used as a fallback if the player has no pressable ingredient either; otherwise pressing runs.

So holding a container serves, holding an ingredient presses, and the shared binding never blocks itself.

The juicer stores output as a fluid id plus an amount. One juicer should only contain one fluid at a time to prevent mixing different drinks. The fluid amount must reach serving_ml before a serving can be bottled.

Fermentation barrel example

yaml
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
FieldDescription
pause_when_openWhether fermentation pauses while the GUI is open.
only_recipe_itemsWhether only recipe ingredients can be inserted.

interactions.serve is a dead key

The fermentation barrel only dispatches open, start, and inspect; serve takes no part in the decision. The default config.yml still carries the key, but changing it has no effect.

Collection is dispatched by the start binding according to the current state:

  • Completed: collect the normal completion output.
  • Fermenting and still within the early stage: collect the early output.
  • Otherwise: start fermenting.

In other words, to change the collection key, change interactions.start.

Fermentation barrels are suited for long-duration, multi-input recipes. Recipes can define early collection, normal completion, and over-fermented results.

Lifecycle

  1. The player interacts with the station using a configured click.
  2. The module checks permission, cooldown, occupancy, and held-item requirements.
  3. A GUI or interaction state is opened.
  4. The player inserts items, adds fuel/moisture, presses, or starts fermenting.
  5. The station tracks progress such as cuts, grinding time, steam, heat, fluid amount, or fermentation stage.
  6. Inputs are consumed and results are generated, or the station enters a completed-but-unclaimed state.
  7. Actions run and temporary state is cleared. Remaining fluid, fuel, or progress may be kept.

Safety notes

  • Avoid multiple players mutating the same item cache at the same time.
  • If cooperation is allowed, define who receives results and who pays the inputs.
  • Station state is persisted through block-entity PDC when possible; normal blocks use the data/stations/ YAML fallback.
  • data/stations/index/<world>.idx records coordinates and backend so ChunkLoad restores inventories, progress, and displays by index while ChunkUnload drops runtime caches.
  • If a block entity is removed or replaced directly, the index may still point to the old PDC station; use /ec inspect block to check backend/PDC state, then /ec station reindex to rebuild the index.
  • For juicers, ovens, and fermentation barrels, verify fluid amount, baking stage, and fermentation progress serialization.

Display entities

Station inputs, intermediates, and results can be shown in the world through display entities, controlled by display_entities and display_adjustments.

FieldDescription
display_entities.backendDisplay backend: auto, packet_events, or bukkit.
display_entities.view_distance_blocksViewer distance in blocks.
display_entities.refresh_interval_ticksDisplay refresh interval.
display_adjustments.defaults.item / defaults.blockGlobal defaults for item-style and block-style displays.
display_adjustments.station_defaults.<station>Per-station overrides.

display_entities.text controls the runtime text display entity (result / progress / next-step hints):

FieldDescription
text.enabledWhether text display entities are used.
text.billboardFacing mode: fixed, vertical, horizontal, center.
text.line_widthMaximum text line width in pixels.
text.backgroundBackground color as an ARGB integer; 0 means fully transparent.
text.shadowWhether text shadow is rendered.
text.see_throughWhether the text is visible through blocks.
text.defaults.offset / text.defaults.scaleText offset and scale relative to the station block.
text.stations.<station>.enabledPer-station toggle; enabled when omitted.

Item display adjustments

The item_adjustments/ directory sets per-item display parameters for specific stations. File names are not used for matching; the item source parsed from item_sources inside the file is the key, and only the last loaded file wins for a given source.

yaml
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
FieldDescription
item_sourcesMatching item sources. The parsed shorthand becomes the key for this adjustment.
adjustmentOptional shared adjustment for all stations. When this section is omitted, offset / rotation / scale are read from the file root instead.
stations.<station>Per-station override, keyed by station folder name.
offset.x/y/zPosition offset relative to the station block.
rotation.x/y/zRotation angles. Range strings such as "-28.0-18.0" pick a random value.
scale.x/y/zScale factors.

A file is skipped when it has neither a shared adjustment nor any recognizable station adjustment.

Station permissions

PermissionDescription
emakicooking.station.chopping_board.useUse the chopping board.
emakicooking.station.chopping_board.cutPerform chopping-board cuts.
emakicooking.station.wok.useUse the wok.
emakicooking.station.wok.stirStir.
emakicooking.station.wok.serveServe.
emakicooking.station.grinder.useUse the grinder.
emakicooking.station.steamer.useUse the steamer.
emakicooking.station.steamer.fuelAdd fuel to the steamer.
emakicooking.station.steamer.moistureAdd moisture to the steamer.
emakicooking.station.oven.useUse the oven.
emakicooking.station.oven.fuelAdd fuel to the oven.
emakicooking.station.juicer.useUse the juicer.
emakicooking.station.juicer.pressPress ingredients.
emakicooking.station.juicer.collectBottle juicer results.
emakicooking.station.fermentation_barrel.useUse the fermentation barrel.
emakicooking.station.fermentation_barrel.startStart fermentation.
emakicooking.station.fermentation_barrel.collectCollect fermentation results.

NOTE

All station permissions default to true. Individual recipes can further restrict access through their own permission field.

GUI configuration

The steamer, oven, juicer, and fermentation barrel have a GUI; the chopping board, grinder, and wok are direct-interaction only. Each GUI station maps to one file under gui/: gui/steamer.yml, gui/oven.yml, gui/juicer.yml, and gui/fermentation_barrel.yml.

yaml
gui_type: CHEST
title: "<dark_gray>Steamer"
rows: 1
slots:
  ingredient_slots:
    slots:
      - 0
      - 1
      - 2
      - 3
      - 4
    type: "ingredient"
FieldDescription
gui_typeContainer type; all four shipped files use CHEST.
titleInventory title. Supports MiniMessage.
rowsRow count, clamped to 1-6 after reading. Defaults: 1 for steamer / oven / juicer, 3 for the fermentation barrel.
slots.<group>.slotsSlot indexes used by this group. Indexes beyond rows × 9 are ignored.
slots.<group>.typeSlot group type. Only ingredient is implemented: the parser walks each child section under slots and skips any group whose type is non-empty and not ingredient (an omitted type counts as ingredient).

The group name itself takes no part in the decision and only serves readability. If slots is empty or every group is skipped, the station falls back to the first N slots (N=5 for steamer / oven / juicer, N=7 for the fermentation barrel).

Testing checklist

  • Players without permission cannot open or use the station.
  • Breaking a station returns, drops, or processes materials according to configuration.
  • Closing a GUI leaves the state as expected.
  • Full inventory, reload, death, and disconnect do not duplicate or delete items.
  • Multiple players clicking the same station do not create race conditions.
  • Oven results are correct for low heat, valid heat, high heat, and overbaking.
  • Juicer behavior is correct for insufficient fluid, enough fluid, wrong containers, and mixed-fluid attempts.
  • Fermentation behavior is correct for early collection, normal completion, over-fermentation, and GUI pause.