Skip to content

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: timed

What 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) ​

SectionPurposeKey content
spawn_pointEntry location of the runlocation (x / y / z / yaw)
roomsRoom topology (list)id, order, region, arrival point
checkpointsCheckpointsregion, point, min_players, record_id
respawn_pointsRespawn pointspoint, linked checkpoint
exit_pointsExitspoint, need_confirm requires a second confirmation
gatesGatesdoor_regions blocks, conditions, logic (AND/OR), open_mode, redstone point
spawn_pointsSpawn pointsspawn_id, point, range, min_players, once, waves list
trigger_zonesTrigger zonesregion, direction (enter/leave), conditions
block_interactionsBlock interactionspoint, click, conditions, actions
rewardsReward pointspoint, loot_table id, conditions
boss_pointsBoss pointspoint, source, boss_id, conditions
optionsBranch optionsoption_id, select_mode (single/multi), cases, time_limit_seconds, min_select/max_select, safe_point, ending_map
hintsHint pointspoint + region, text, channel, conditions
teleport_zonesTeleport zonesregion -> target, all_members
boundary_zonesBoundary zonesregion + pullback point
effect_zonesEffect zonesregion, effects, interval_ticks
mechanismsMechanismstargets block-state list (block_data + point), conditions
room_timersRoom timersregion, seconds, on_zero action
kill_targetsKill targetstarget, count, scope region, actions
item_interactionsItem interactionspoint, item, amount, consume, conditions, actions
fx_pointsFX pointspoint + region, sound, particle, radius, conditions
temp_itemsTemporary itemsitem, 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:

TypeJudgesCommon params
rangeEnough targets inside rangemin_players, target (player/mob)
regionEntered or left a regiondirection (enter/leave)
block_interactBlock interaction click matchesclick
block_activateBlock activated (button, lever, ...)—
redstoneRedstone level matches; change means "changed"level
clearThe waves of a spawn point are clearedspawn
boss_killThe specified boss is deadboss_id
kill_countKill count reachedtarget, count
timerThe specified timer module firedmodule
itemAn item interaction happened—
commandCommand result matchesexpected
player_statePlayer state matches (optionally with remaining lives)state, remaining_lives
scriptVerdict 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:

TypeEffectCommon params
hintPush a hint to the playertext, channel
messageSend a message by lang keykey, channel
commandRun a console command (player placeholder supported)command
teleportTeleport the playercoordinates
fxPlay effectssound / particle related
set_blockChange a block stateblock_data + point
open_gateOpen a gategate id
spawn_waveStart a spawn point's wavespawn point id
failFail the runcause
completeClear the runcause
give_temp_itemGrant 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:

  1. A prompt appears: "choose: ... (enter a number or id)"
  2. Then a second confirmation: "chosen ... type confirm to lock it in (times out to auto-lock), type cancel to abort"
  3. The count must fall within min_select~max_select; single for single choice, multi for multiple
  4. time_limit_seconds is the selection timer, 0 means 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:

  1. Markers: single-point markers (spawn/option/chest) go on the aimed block, region markers (room/gate/trigger) use pos1 + pos2; marks lists what is staged
  2. Panels: the main panel has four areas - dungeon metadata, spawn points and waves, gates, and branch options; field edits are staged first
  3. Terrain: fill <block> fills the circled area frame by frame
  4. Test: test runs a normal entry from the saved template, counts nothing and never ranks; refused while unsaved changes exist
  5. Save: "Save and write back" persists markers and panel edits into config.yml / flow.yml plus terrain, echoing a new generation number; exit quits 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 run
  • onTrigger('onEnter', ...) / waitFor('onRoomEnter') subscribe and await triggers (such as onEnter, onRoomEnter, onWaveClear, onRoomClear, onBossKill, onGateOpen)
  • dungeon.spawnWave(...) / dungeon.openGate(...) / dungeon.enterRoom(...) / dungeon.complete(...) drive the flow
  • dungeon.registerEntry(...) register temporary entries for everyone (removed when the run ends), dungeon.hint(...) push hints
  • dungeon.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