Per-player instances
Each player who triggers an encounter gets their own runtime state. Other players nearby see nothing.
Encounters are Journey’s framework for instanced, per-player (or per-party) gameplay set-pieces — dungeons, boss fights, puzzles, scripted sequences. Each player who walks into an encounter gets their own runtime instance with its own spawned mobs, fake blocks, and phase state. Other players see nothing (or only the participants see the action, depending on the visibility tier).
New in Beta 2 Authored via an in-game visual editor or hand-edited JSON.
Per-player instances
Each player who triggers an encounter gets their own runtime state. Other players nearby see nothing.
Signal-wired elements
27 built-in element types emit named signals that sibling elements listen for. Activate, deactivate, transition phase, run a script — all declarative JSON.
Multi-phase progression
Phase definitions gate which elements are active, drive signal listeners, and switch fake block sets as the encounter unfolds.
In-game visual editor
/journey editor new <name> hands you a 9-tool hotbar to place, wire, and phase encounters without editing JSON by hand.
An encounter is a JSON blueprint pinned to a world location. When a player steps inside the activation radius, Journey spins up a private instance just for them (and their party), ticks it for as long as they’re in range, and tears it down when they complete it, die, or walk away. Inside the instance, you have elements — configurable objects like spawners, objectives, barriers, timers — that emit and listen for signals to wire up custom behavior.
There are two flavors:
| Field | Type | Default | Description |
|---|---|---|---|
name | String | "" | Display name |
description | String[] | [] | Lore lines |
icon | Icon | — | Reuses the task system icon type |
origin | {x,y,z} | {0,0,0} | World position; activation radius is centered here |
radius | Double | 30.0 | Proximity activation radius. Players are dropped out at radius * 2 |
dimension | String | "minecraft:overworld" | Dimension resource location |
elements | Element[] | [] | Members of the encounter |
phases | Object | null | Phase map. First key is the entry phase. |
reset | ResetConfig | — | mode ∈ cooldown / one_time / daily / always, plus cooldown_ticks |
rewards | RewardConfig[] | null | Each { type, data }. Only type == "command" is implemented today |
blocks | Map | {} | Encounter-level fake blocks, visible regardless of phase |
visibility | String | "all" | Default visibility tier for spawned entities |
persistent | Boolean | false | If true, the instance isn’t auto-destroyed on range exit |
persistent_mode | String | "shared" | "shared" or "per_player" |
blueprints | Object | null | Reusable bundles: spawn tables, loot tables, item blueprints, dialogues, sound packs, command pools, reward packs, variables |
The ID is derived from the file basename (e.g. stone_tower.json → journey:stone_tower).
| Field | Type | Notes |
|---|---|---|
name | String | Display name |
duration | Long | Phase duration in ticks. Drives on_duration_expire transitions |
active_elements | String[] | Whitelist of element IDs active in this phase. Falls back to each element’s active_in_phases list when null |
transitions | PhaseTransition[] | Outbound transitions |
blocks | Map | Phase-only fake blocks (additive to the encounter-level blocks) |
| Field | Notes |
|---|---|
target | Phase name, or complete / success / completed / failure / failed |
on_signal | Signal pattern listened for while this is the current phase |
on_duration_expire | Fires when elapsed phase ticks meet the phase duration |
on_all_objectives_complete | Fires when every active objective element with required = true is complete |
Elements emit named signals. Other elements (and phase definitions) listen for those signals and react. Wiring is declarative JSON — no code, no scripts needed for most cases.
Each element’s signals list is a set of {on, action, target?, data?} entries. When a matching signal arrives, Journey performs the action.
| Action | Effect |
|---|---|
activate | Resolve target, activate it |
deactivate | Resolve target, deactivate it |
emit | Re-emit a signal whose name comes from data.signal |
transition | Request a phase transition to target |
script | Run data.script through a Molang runtime |
store_set | Set a key in the encounter data store |
store_increment / _decrement / _multiply | Numeric mutators |
store_toggle | Boolean toggle |
| anything else | Falls through to the element for type-specific actions (go_to_point, start_wave, set_stage, etc.) |
Signal patterns are "sourceId:signalName" strings:
wave_1:all_dead — match source and name exactlyall_dead — match any source for that namewave_1:* — match any signal from that source*:entity_killed — match any source for that name{ "elements": [ { "id": "wave_1", "type": "wave_spawner", "signals": [ { "on": "all_dead", "action": "activate", "target": "boss_door" } ] }, { "id": "boss_door", "type": "barrier", "initially_active": true, "signals": [ { "on": "objective_complete", "action": "deactivate", "target": "boss_door" }, { "on": "boss:entity_killed", "action": "transition", "target": "victory" } ] }, { "id": "boss", "type": "spawner" } ], "phases": { "fight": { "transitions": [ { "target": "victory", "on_signal": "boss_dead" } ] }, "victory": { } }}When every wave 1 entity dies, the boss door deactivates. When the boss’s entity_killed signal fires, the encounter transitions to the victory phase. Done — all declarative JSON.
A definition with a non-empty phases map becomes phased — the first key is the entry phase. Each phase has its own active element whitelist, its own fake blocks, and its own outbound transitions.
Phase transitions can target another phase by name, or use the special targets below:
complete / success / completed → the encounter finishes and distributes rewardsfailure / failed → the encounter is destroyed without running rewards"phases": { "wave_1": { "duration": 600, "active_elements": ["spawner_1"], "transitions": [ { "target": "wave_2", "on_signal": "all_dead" } ] }, "wave_2": { "duration": 600, "active_elements": ["spawner_2"], "transitions": [ { "target": "boss", "on_signal": "all_dead" } ] }, "boss": { "active_elements": ["boss_spawner"], "transitions": [ { "target": "victory", "on_signal": "boss_dead" }, { "target": "failed", "on_duration_expire": true } ] }, "victory": { }}Wave 1, wave 2, boss fight, victory (or fail on time-out). Four phases, each with its own active element whitelist, gated by signal and duration.
Encounter entities can be hidden from non-participants using the visibility field on each element (or on the encounter as a default). Four values are supported:
| Tier | Behavior |
|---|---|
"all" | Everyone in the world sees the entity. Fine for ambient set dressing, but not for participant-only mobs. |
"eligible" / "party" | Only the owner and their party see the entity. Other players in the same area see nothing. |
"none" | Nobody sees the entity except admins. Useful for trigger helpers and invisible logic elements. |
When a managed encounter mob dies, its item and XP drops inherit the same viewer set — only participants can see or pick them up. Mobs with "all" visibility fall through to vanilla loot rules.
Two persistence modes encoded by persistent and persistent_mode:
| Mode | Behavior |
|---|---|
persistent: false | Default. Per-player instances, destroyed when the owner walks away or disconnects. |
persistent: true, persistent_mode: "per_player" | Per-player instances that stay alive (but stop updating) when the owner walks away. |
persistent: true, persistent_mode: "shared" | One global instance visible to everyone in range. Created automatically at server start. Never auto-destroyed. |
| Path | Mechanism |
|---|---|
| Proximity | Automatic — players within radius activate non-shared definitions. |
| Persistent shared (auto-start) | Server start creates instances for persistent && persistent_mode == "shared". |
| Admin command | /journey encounter start <id> [player] |
| Editor Test tool | Editor hotbar slot 7. |
| Molang (inside an active encounter) | Scripts running inside an encounter can activate / deactivate elements, request phase transitions, emit signals, and mutate the data store. |
| Tasks / NPCs / dialogues / zones | No direct API today — shell out to /journey encounter start from a command reward or script. |
The editor is invoked through /journey editor subcommands (requires journey.command.admin / OP level 3). It’s the fastest way to author encounters without hand-writing JSON.
| Subcommand | Behavior |
|---|---|
new <name> | Start a new editor session at the player’s position. Names must match ^[a-zA-Z0-9_:.-]+$. |
edit <name> | Open an existing definition for editing. Autocompletes from all loaded definitions. |
cancel | Discard pending changes, revert blocks, restore player state. |
save | Save and exit (equivalent to hotbar slot 8). |
When you start an editor session, Journey captures your current gamemode, inventory, and position, flips you to creative, and hands you a 9-tool hotbar. save and cancel both restore everything.
| Slot | Item | Tool | Right-click | Shift+Right-click |
|---|---|---|---|---|
| 0 | Spawner | Place | Place element at crosshair | Cycle placement type |
| 1 | Spectral Arrow | Select | Cycle-select nearest element within 10 blocks | Move selected to crosshair; left-click adjusts Y or rotation |
| 2 | Comparator | Configure | Open the type-specific config GUI | Toggle block-assign mode for structure / platform / barrier |
| 3 | Lead | Wire | First click = wire source; second = target, opens the wiring editor | Open the wiring list / cancel pending wire |
| 4 | Clock | Phases | Open the phase editor | — |
| 5 | Barrier | Delete | Delete selected element (cleans up referencing wirings) | Clear selection |
| 6 | Beacon | Settings | Open encounter-level settings | — |
| 7 | Ender Eye | Test | Save and start the encounter for yourself | — |
| 8 | Structure Block | Save & Exit | Save atomically and hot-reload | — |
Press F (or the swap-hands key) to open the element type picker, or to toggle between Y-Adjust and Rotate sub-modes on slot 1.
The editor ships a full suite of sub-editors:
wave_spawner elementsAll encounter commands require journey.command.admin (OP level 3 fallback).
/journey encounter ...| Subcommand | Behavior |
|---|---|
start <id> [player] | Force-start an encounter for self or named player. Still respects cooldown. |
stop <instanceId> | Destroy an instance by UUID |
list [definitions|active] | List loaded definitions or all active instances |
info <id> | Print origin, radius, dimension, element list, phase list, reset config |
reload | Re-scan encounter definitions |
reset [id] | Destroy and re-create the calling player’s matching active encounter(s) |
debug | Toggle signal bus debug echo for the calling player |
/journey editor ...| Subcommand | Behavior |
|---|---|
new <name> | Start a new editor session |
edit <name> | Open an existing definition |
cancel | Discard and restore |
save | Save and exit |
Two distinct reward concepts:
On completion, the encounter’s rewards list runs only for the owner, only if they’re still online. Currently only command rewards are implemented — the {player} placeholder is substituted and the command runs with suppressed output. Other reward types parse without error but are silently skipped.
Encounter mob drops inherit the mob’s visibility set, so only participants see and can pick up the drops. This works for both item drops and experience orbs.
Each instance owns a key-value data store seeded from blueprints.variables and mutated by signal-wiring store_* actions. The store uses type coercion driven by variable definitions (string, int, double, bool). This is the canonical way to share state between elements without relying on per-element custom data.
radius * 2, except for persistent_mode == "per_player" which only stops updating.position and offset set — absolute position wins.on_signal transitions pair well with emitter elements (wave spawner, objective, sequence).store_set / store_increment wiring actions are more robust than chaining custom-data mutations through scripts.persistent: false. Per-player scoping is the default for a reason; escalate only when you genuinely need a global instance.wave_1_spawner over spawner1 to avoid collisions when copy-pasting phases.