Skip to content

Interactables

Interactables are invisible entities placed in the world that players can click to trigger MoLang scripts. Think of them as invisible buttons — right-click to interact, left-click to attack, with per-player visibility so each player sees different things based on their progress.


Interactable configs go in config/journey/interactables/.

{
"id": "journey:quest_starter",
"right_click_script": [
"q.player.tell_minimessage('<green>Quest started!');",
"q.player.start_task('journey:main_quest');",
"q.player.add_flag('quest_started');",
"q.set_interaction_result('success');"
],
"left_click_script": "q.player.tell_minimessage('<gray>Try right-clicking!');",
"visibility_script": "!q.player.has_flag('quest_started')",
"width": 1.0,
"height": 2.0
}
FieldTypeDefaultDescription
idStringRequiredUnique identifier (e.g., "journey:quest_trigger")
right_click_scriptString or Array""Script(s) to run on right-click
left_click_scriptString or Array""Script(s) to run on left-click (attack)
visibility_scriptString""MoLang condition controlling per-player visibility
widthFloat1.0Hitbox width in blocks
heightFloat1.0Hitbox height in blocks

Scripts can be a single string or an array of strings. Each statement should end with a semicolon.


Control what happens after a player interacts using q.set_interaction_result():

ResultDescription
'success'Standard success
'success_no_item_used'Success without consuming held item
'consume'Consume the interaction (prevents item use)
'consume_partial'Partial consumption
'pass'Pass through to other interactions
'fail'Fail the interaction

Always set a result at the end of your scripts:

"right_click_script": [
"q.player.add_flag('button_pressed');",
"q.player.tell_minimessage('<green>Button pressed!');",
"q.set_interaction_result('consume');"
]

The visibility_script controls who can see and interact with the entity. It’s evaluated per-player.

{"visibility_script": "!q.player.has_flag('collected_coin')"}
{"visibility_script": "q.player.has_completed_task('journey:tutorial')"}
{"visibility_script": "q.player.levelable_level('Combat') >= 25.0"}
{"visibility_script": "1.0 == 1.0"}

When the condition returns 0.0 (false), the entity is completely hidden from that player.


Use the command to place interactables in the world:

/journey interactable summon <id> <position>

Examples:

/journey interactable summon journey:quest_starter ~ ~ ~
/journey interactable summon journey:secret_coin ~ ~1 ~
/journey interactable summon journey:locked_portal 100 64 -200

Permission: journey.command.interactable

Interactables persist across server restarts. Use q.this.uuid in scripts to reference the specific entity’s UUID.


Invisible trigger that starts a quest, then disappears:

{
"id": "journey:quest_starter",
"right_click_script": [
"q.player.tell_minimessage('<green>Quest started!');",
"q.player.start_task('journey:main_quest');",
"q.player.add_flag('quest_started');",
"q.set_interaction_result('success');"
],
"visibility_script": "!q.player.has_flag('quest_started')",
"width": 1.0,
"height": 2.0
}

Requires 5 diamonds and quest completion to teleport:

{
"id": "journey:locked_portal",
"right_click_script": [
"q.player.has_item('minecraft:diamond', 5) ? (",
" q.player.remove_item('minecraft:diamond', 5);",
" q.player.execute_command('tp {player} 100 64 200');",
" q.player.tell_minimessage('<aqua>Portal activated!');",
" q.set_interaction_result('success');",
") : (",
" q.player.tell_minimessage('<red>You need 5 diamonds to use this portal.');",
" q.set_interaction_result('fail');",
")"
],
"visibility_script": "q.player.has_completed_task('journey:unlock_portal')",
"width": 2.0,
"height": 3.0
}

Small coin that disappears after collection:

{
"id": "journey:secret_coin",
"right_click_script": [
"q.player.progress_levelable('Collector', 1);",
"q.player.tell_minimessage('<gold>+1 Secret Coin!');",
"q.player.add_flag('coin_' + q.this.uuid);",
"q.set_interaction_result('consume');"
],
"visibility_script": "!q.player.has_flag('coin_' + q.this.uuid)",
"width": 0.5,
"height": 0.5
}

Part of a 3-button puzzle tracked with flags:

{
"id": "journey:puzzle_button_1",
"right_click_script": [
"!q.player.has_flag('button_1') ? q.player.add_flag('button_1') : 0;",
"q.player.has_flag('button_1') && q.player.has_flag('button_2') && q.player.has_flag('button_3') ? (",
" q.player.tell_minimessage('<green>Puzzle solved!');",
" q.player.start_task('journey:puzzle_reward');",
") : (",
" q.player.tell_minimessage('<yellow>Button pressed. Find the other buttons.');",
");",
"q.set_interaction_result('success');"
],
"visibility_script": "q.player.has_completed_task('journey:puzzle_quest')",
"width": 1.0,
"height": 1.0
}

Use CaseWidthHeight
Small collectible0.50.5
Standard button1.01.0
NPC-sized trigger1.02.0
Door or portal2.03.0
Large interaction zone3.03.0

Use F3+B in-game to see entity hitboxes for testing.


  • Always set an interaction result at the end of your scripts
  • Always give the player feedback (tell_minimessage) so they know something happened
  • Use flags for one-time interactions and visibility toggling
  • Validate conditions before performing actions (check items before removing them)
  • Interactables are invisible — use nearby particles, markers, or items to hint at their location
  • Keep scripts simple; for complex logic, trigger a timeline instead