English
Flow & Triggers
flow.yml is the dungeon's gameplay blueprint: how rooms advance, how many waves spawn, when gates open, and which path players take after a choice. It lives at plugins/EmakiDungeon/world/<dungeonId>/flow.yml and is generated from the bundled template when missing. Module IDs must be unique inside a dungeon; duplicates and out-of-bounds definitions are validated at load time, and a failing load falls back to an empty flow for that instance — run /edg admin validate to health-check all templates up front
Most people never hand-write it: place markers with the map editor in game and save to have them written into flow.yml. The generated structure and how to wire modules together with conditions and actions are explained below
File skeleton
The bundled template looks like this (excerpt from the real file):
yaml
version: "1.0.0"
rooms: {}Entry-point convention: never write a spawn_point override section in a new dungeon's flow.yml (the bundled default template no longer ships it). The entry point falls back to the world spawn: set the template world spawn to a clear landing spot with /setworldspawn and make sure the spawn stored in the template level.dat is valid (the server validates the stored spawn on world load and may re-pick a different position otherwise). After exporting the template, verify flow has no such section, then confirm the actual landing with one real entry. Region-based modules that depend on the entry position (such as entry_timer's region) must cover the real landing spot. spawn_point remains available as an explicit override — once written, entry, respawn and settlement all use it
Every module follows a two-level shape: "section name -> { module id -> module body }", and the body carries its own id and enabled switch. Here is a room timer from a tested dungeon:
yaml
room_timers:
entry_timer:
id: entry_timer
enabled: true
region:
min:
x: -20.0
y: 55.0
z: -20.0
max:
x: 20.0
y: 80.0
z: 20.0
seconds: 15
on_zero:
type: complete
cause: timedWhat this entry means: once players enter the region a 15-second countdown starts, and on zero the complete action ends the run. Regions (region) are always two corner coordinates, min and max
Module catalog (22 types)
| Section | Purpose | Key content |
|---|---|---|
spawn_point | Entry location of the run | location (x / y / z / yaw) |
rooms | Room topology (list) | id, order, region, arrival point |
checkpoints | Checkpoints | region, point, min_players, record_id |
respawn_points | Respawn points | point, linked checkpoint |
exit_points | Exits | point, need_confirm requires a second confirmation |
gates | Gates | door_regions blocks, conditions, logic (AND/OR), open_mode, redstone point |
spawn_points | Spawn points | spawn_id, point, range, min_players, once, waves list |
trigger_zones | Trigger zones | region, direction (enter/leave), conditions |
block_interactions | Block interactions | point, click, conditions, actions |
rewards | Reward points | point, loot_table id, conditions |
boss_points | Boss points | point, source, boss_id, conditions |
options | Branch options | option_id, select_mode (single/multi), cases, time_limit_seconds, min_select/max_select, safe_point, ending_map |
hints | Hint points | point + region, text, channel, conditions |
teleport_zones | Teleport zones | region -> target, all_members |
boundary_zones | Boundary zones | region + pullback point |
effect_zones | Effect zones | region, effects, interval_ticks |
mechanisms | Mechanisms | targets block-state list (block_data + point), conditions |
room_timers | Room timers | region, seconds, on_zero action |
kill_targets | Kill targets | target, count, scope region, actions |
item_interactions | Item interactions | point, item, amount, consume, conditions, actions |
fx_points | FX points | point + region, sound, particle, radius, conditions |
temp_items | Temporary items | item, amount, timing, hint; taken back when the instance ends |
Each wave inside a spawn point's waves carries interval_ticks (spacing), drop_loot (whether the wave drops loot), and mobs entries (id / count / weight); mob ids accept vanilla entity ids and the emakimobs: prefix (requires EmakiMobs)
Conditions
A module's conditions is a list where each entry is judged by its type with params flattened alongside, and you can nest entries with logic (AND/OR) plus children. An unknown condition type always evaluates to false:
| Type | Judges | Common params |
|---|---|---|
range | Enough targets inside range | min_players, target (player/mob) |
region | Entered or left a region | direction (enter/leave) |
block_interact | Block interaction click matches | click |
block_activate | Block activated (button, lever, ...) | — |
redstone | Redstone level matches; change means "changed" | level |
clear | The waves of a spawn point are cleared | spawn |
boss_kill | The specified boss is dead | boss_id |
kill_count | Kill count reached | target, count |
timer | The specified timer module fired | module |
item | An item interaction happened | — |
command | Command result matches | expected |
player_state | Player state matches (optionally with remaining lives) | state, remaining_lives |
script | Verdict written by the dungeon script | — |
Actions
Once conditions hold, the listed actions run the same way (type plus sibling params); timers hang theirs on on_zero:
| Type | Effect | Common params |
|---|---|---|
hint | Push a hint to the player | text, channel |
message | Send a message by lang key | key, channel |
command | Run a console command (player placeholder supported) | command |
teleport | Teleport the player | coordinates |
fx | Play effects | sound / particle related |
set_block | Change a block state | block_data + point |
open_gate | Open a gate | gate id |
spawn_wave | Start a spawn point's wave | spawn point id |
fail | Fail the run | cause |
complete | Clear the run | cause |
give_temp_item | Grant a temporary item (removed at instance end) | item and amount |
Dungeon-level lifecycle actions (dungeon_enter and friends, 11 segments) are not written in flow; they live in the main configuration's action section — see Configuration
Branch options and endings
The options section defines the forks in front of the players, and the whole interaction happens in chat:
- A prompt appears: "choose: ... (enter a number or id)"
- Then a second confirmation: "chosen ... type
confirmto lock it in (times out to auto-lock), typecancelto abort" - The count must fall within
min_select~max_select;singlefor single choice,multifor multiple time_limit_secondsis the selection timer,0means none; timing out counts as no choice
Each candidate (cases) carries its label, its ending id, and seal_regions seal areas: losing paths get sealed and players approaching a sealed area are pulled back to that candidate's safe_point. Candidates combine through ending_map into the final ending. Firing order: DungeonChoiceEvent before the prompt is even shown (cancellable - cancel and no prompt appears), DungeonOptionConfirmEvent when locked in, DungeonSealPathEvent when a path seals, and DungeonEndingEvent at the ending (full list on API)
Map editor
Run /edg editor <dungeonId> (needs emakidungeon.editor) to enter the dedicated edit world; then:
- Markers: single-point markers (
spawn/option/chest) go on the aimed block, region markers (room/gate/trigger) usepos1+pos2;markslists what is staged - Panels: the main panel has four areas - dungeon metadata, spawn points and waves, gates, and branch options; field edits are staged first
- Terrain:
fill <block>fills the circled area frame by frame - Test:
testruns a normal entry from the saved template, counts nothing and never ranks; refused while unsaved changes exist - Save: "Save and write back" persists markers and panel edits into
config.yml/flow.ymlplus terrain, echoing a new generation number;exitquits and discards unsaved changes
Only one editing session per template at a time; you cannot open the editor while inside an instance or a challenge flow - finish those first; leftover edit worlds are swept on plugin startup
GraalJS dungeon scripts
scripts/<dungeonId>.js is loaded with every instance of that dungeon and released when the instance is destroyed (switch and timeout live in the global script section). In a script you can:
dungeon.takeover()take over pacing of the whole runonTrigger('onEnter', ...)/waitFor('onRoomEnter')subscribe and await triggers (such asonEnter,onRoomEnter,onWaveClear,onRoomClear,onBossKill,onGateOpen)dungeon.spawnWave(...)/dungeon.openGate(...)/dungeon.enterRoom(...)/dungeon.complete(...)drive the flowdungeon.registerEntry(...)register temporary entries for everyone (removed when the run ends),dungeon.hint(...)push hintsdungeon.factFlag(...)/dungeon.setFact(...)pass state between script facts
A script error or timeout only disables that script while the instance keeps running (unless you turn on script.fail-on-error); a complete example ships as examples/example_flow.js in the plugin resources. Third-party plugins can register script triggers through the API — see API