Interactables
Interactables
Section titled “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.
Configuration
Section titled “Configuration”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}| Field | Type | Default | Description |
|---|---|---|---|
id | String | Required | Unique identifier (e.g., "journey:quest_trigger") |
right_click_script | String or Array | "" | Script(s) to run on right-click |
left_click_script | String or Array | "" | Script(s) to run on left-click (attack) |
visibility_script | String | "" | MoLang condition controlling per-player visibility |
width | Float | 1.0 | Hitbox width in blocks |
height | Float | 1.0 | Hitbox height in blocks |
Scripts can be a single string or an array of strings. Each statement should end with a semicolon.
Interaction Results
Section titled “Interaction Results”Control what happens after a player interacts using q.set_interaction_result():
| Result | Description |
|---|---|
'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');"]Visibility System
Section titled “Visibility System”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.
Spawning
Section titled “Spawning”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 -200Permission: journey.command.interactable
Interactables persist across server restarts. Use q.this.uuid in scripts to reference the specific entity’s UUID.
Complete Examples
Section titled “Complete Examples”Quest Starter
Section titled “Quest Starter”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}Item-Gated Portal
Section titled “Item-Gated Portal”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}Hidden Collectible
Section titled “Hidden Collectible”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}Puzzle Button
Section titled “Puzzle Button”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}Hitbox Sizing Guide
Section titled “Hitbox Sizing Guide”| Use Case | Width | Height |
|---|---|---|
| Small collectible | 0.5 | 0.5 |
| Standard button | 1.0 | 1.0 |
| NPC-sized trigger | 1.0 | 2.0 |
| Door or portal | 2.0 | 3.0 |
| Large interaction zone | 3.0 | 3.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