Interactables System
Interactables System
Section titled “Interactables System”Interactables are invisible entities that players can interact with by clicking. They execute custom MoLang scripts on interaction and can have per-player conditional visibility, making them perfect for invisible triggers, quest interactions, and dynamic world content.
Overview
Section titled “Overview”Interactables provide:
- Invisible Hitboxes: Customizable width and height for interaction zones
- Click Actions: Different scripts for right-click (interact) and left-click (attack)
- Per-Player Visibility: MoLang-based conditional visibility per player
- MoLang Integration: Full access to Journey’s scripting capabilities
- Persistent: Save with world data across server restarts
Common Use Cases:
- Quest trigger zones that activate on click
- Invisible NPCs or objects for story events
- Conditional doors/portals requiring items or progress
- Hidden collectibles that disappear after collection
- Multi-stage puzzles using flags
Configuration Structure
Section titled “Configuration Structure”Interactables are defined in JSON files located in config/journey/interactables/
{ "id": "journey:test", "right_click_script": "q.player.tell('right click!');", "left_click_script": [ "q.player.tell('left click!');", "q.set_interaction_result('consume');" ], "visibility_script": "q.player.has_flag('vis_test')", "width": 1.25, "height": 1.0}Fields
Section titled “Fields”| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | String | Yes | - | Unique identifier (e.g., "journey:quest_trigger") |
right_click_script | String/Array | No | "" | MoLang script(s) executed on right-click |
left_click_script | String/Array | No | "" | MoLang script(s) executed on left-click (attack) |
visibility_script | String/Array | No | "" | MoLang expression determining player visibility (boolean) |
width | Float | No | 1.0 | Interaction hitbox width in blocks |
height | Float | No | 1.0 | Interaction hitbox height in blocks |
Scripts
Section titled “Scripts”Scripts can be single strings or arrays of strings. Each statement should end with a semicolon.
Single Script
Section titled “Single Script”{ "right_click_script": "q.player.tell_minimessage('<green>Quest started!');"}Multiple Scripts
Section titled “Multiple Scripts”{ "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');" ]}Interaction Results
Section titled “Interaction Results”Control how interactions behave using q.set_interaction_result():
q.set_interaction_result('consume') // Prevent item useq.set_interaction_result('success') // Standard successq.set_interaction_result('fail') // Fail interactionq.set_interaction_result('pass') // Allow other interactionsAvailable Results:
"success"- Standard success"success_no_item_used"- Success without consuming item"consume"- Consume interaction (prevents item use)"consume_partial"- Partial consumption"pass"- Pass through to other interactions"fail"- Fail the interaction
MoLang Functions
Section titled “MoLang Functions”Player Functions
Section titled “Player Functions”Available in all interactable scripts via q.player.*:
Messaging:
q.player.tell_minimessage(message) // Send formatted messageq.player.username // Get player usernameFlags & State:
q.player.has_flag(flag) // Check if player has flag (returns 1.0/0.0)q.player.add_flag(flag) // Add flag to playerq.player.remove_flag(flag) // Remove flag from playerTasks/Quests:
q.player.has_completed_task(taskName) // Check task completionq.player.has_completed_subtask(taskName, subtaskName) // Check subtaskq.player.start_task(taskName) // Start a taskItems:
q.player.has_item(itemId, count) // Check if player has itemsq.player.give_item(itemId, count) // Give itemsq.player.remove_item(itemId, count) // Remove itemsPokémon:
q.player.has_party_pokemon_matching(query) // Check for matching Pokémonq.player.remove_party_pokemon(index) // Remove Pokémon at slotq.player.party_slot_for_pokemon(query) // Get party slot indexLevelables:
q.player.has_levelable(name) // Check if player has levelableq.player.give_levelable(name) // Give levelableq.player.levelable_level(name) // Get current levelq.player.levelable_experience(name) // Get current XPq.player.progress_levelable(name, amount) // Add XPq.player.remove_levelable(name) // Remove levelableCommands & Effects:
q.player.execute_command(command) // Execute server commandq.player.launch_timeline(timelineName) // Start timelineq.player.snowstorm_particle(particle, x, y, z) // Spawn particle effectq.player.push(x, y, z, force) // Push playerZones:
q.player.is_in_zone(zoneUUID) // Check if player in zonePaths:
q.player.toggle_path_visualization() // Toggle path previewq.player.show_path_visualization(show) // Set path preview stateq.player.preview_path(pathId) // Preview a pathAttack-Specific Functions
Section titled “Attack-Specific Functions”Available in left_click_script only:
q.damage_source() // Returns damage source type as stringq.damage_amount() // Returns damage amountVisibility System
Section titled “Visibility System”The visibility_script controls who can see and interact with the interactable:
{ "visibility_script": "q.player.has_flag('unlocked_area')"}How it Works:
- Evaluated per-player periodically
- Returns
true(1.0) = player can see it - Returns
false(0.0) = hidden from player - Cached for performance - updates only when state changes
Visibility Examples
Section titled “Visibility Examples”Visible only to players without a flag:
{ "visibility_script": "!q.player.has_flag('collected_coin')"}Visible only to players who completed a task:
{ "visibility_script": "q.player.has_completed_task('journey:tutorial')"}Visible only to high-level players:
{ "visibility_script": "q.player.levelable_level('Combat') >= 25.0"}Visible only with specific item:
{ "visibility_script": "q.player.has_item('minecraft:diamond', 1)"}Always visible:
{ "visibility_script": "1.0 == 1.0"}Hitbox Sizing
Section titled “Hitbox Sizing”Customize the interaction area with width and height:
{ "width": 2.0, // 2 blocks wide "height": 3.0 // 3 blocks tall}Common Sizes:
- Default (1x1): Standard button/trigger
- Small (0.5x0.5): Collectible, precise trigger
- Large (2x3): Door, portal, large interaction zone
- Wide (3x1): Horizontal trigger area
Complete Examples
Section titled “Complete Examples”Example 1: Quest Starter
Section titled “Example 1: Quest Starter”File: config/journey/interactables/quest_starter.json
{ "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}Usage: Invisible trigger that starts a quest, then becomes invisible after use.
Example 2: Item-Gated Portal
Section titled “Example 2: Item-Gated Portal”File: config/journey/interactables/locked_portal.json
{ "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}Usage: Portal requiring quest completion and 5 diamonds to use.
Example 3: Hidden Collectible
Section titled “Example 3: Hidden Collectible”File: config/journey/interactables/secret_coin.json
{ "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.player.snowstorm_particle('cobblemon:sparkle', 0.0, 1.0, 0.0);", "q.set_interaction_result('consume');" ], "left_click_script": [ "q.player.tell_minimessage('<gray>Try right-clicking!');", "q.set_interaction_result('fail');" ], "visibility_script": "!q.player.has_flag('coin_' + q.this.uuid)", "width": 0.5, "height": 0.5}Usage: Small collectible that can only be collected once per player, with particle effect.
Example 4: Battle Trigger
Section titled “Example 4: Battle Trigger”File: config/journey/interactables/trainer_battle.json
{ "id": "journey:trainer_battle", "right_click_script": [ "q.player.execute_command('cobblemon battle {player} trainer:rival');", "q.player.add_flag('fought_rival');", "q.player.tell_minimessage('<red>Rival wants to battle!');", "q.set_interaction_result('success');" ], "visibility_script": "q.player.has_party_pokemon_matching('any') && !q.player.has_flag('fought_rival')", "width": 1.0, "height": 2.0}Usage: Triggers trainer battle, only visible to players with Pokémon who haven’t fought yet.
Example 5: Multi-Button Puzzle
Section titled “Example 5: Multi-Button Puzzle”File: config/journey/interactables/puzzle_button.json
{ "id": "journey:puzzle_button", "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}Usage: Part of a 3-button puzzle tracked with flags.
Example 6: Conditional NPC Dialogue
Section titled “Example 6: Conditional NPC Dialogue”File: config/journey/interactables/mysterious_voice.json
{ "id": "journey:mysterious_voice", "right_click_script": [ "q.player.levelable_level('Wisdom') < 10.0 ? (", " q.player.tell_minimessage('<gray><italic>You hear whispers... but cannot understand.');", ") : (", " q.player.tell_minimessage('<gold>The voice speaks: \"Seek the ancient temple.\"');", " q.player.add_flag('heard_prophecy');", ");", "q.set_interaction_result('success');" ], "visibility_script": "q.player.is_in_zone('temple-zone-uuid')", "width": 2.0, "height": 2.0}Usage: Voice that gives different messages based on player level.
Spawning Interactables
Section titled “Spawning Interactables”Use the Journey command to spawn interactables in the world:
/journey interactable summon <type> <position>Examples
Section titled “Examples”Spawn at your location:
/journey interactable summon journey:quest_starter ~ ~ ~Spawn 1 block above you:
/journey interactable summon journey:secret_coin ~ ~1 ~Spawn at specific coordinates:
/journey interactable summon journey:locked_portal 100 64 -200Permission Required: journey.command.interactable (default OP level 3)
Persistence
Section titled “Persistence”Interactables automatically save with world data:
- Persist across server restarts
- Config changes apply to existing entities on reload
- Entity UUID tracked for per-player flags
- Use
q.this.uuidin scripts to reference entity UUID
Best Practices
Section titled “Best Practices”Script Design
Section titled “Script Design”✅ Always set interaction results:
q.set_interaction_result('success'); // Or 'consume', 'fail', etc.✅ Provide player feedback:
q.player.tell_minimessage('<green>Action completed!');✅ Use flags for one-time interactions:
!q.player.has_flag('collected') ? (...collect...) : (...already collected...)✅ Validate conditions before actions:
q.player.has_item('key', 1) ? (...unlock...) : (...locked...)Visibility
Section titled “Visibility”✅ Hide completed content:
{ "visibility_script": "!q.player.has_flag('completed_puzzle')"}✅ Show only to eligible players:
{ "visibility_script": "q.player.has_completed_task('journey:prerequisite')"}✅ Combine multiple conditions:
{ "visibility_script": "q.player.has_flag('unlocked') && q.player.levelable_level('Mining') >= 10.0"}Hitbox Sizing
Section titled “Hitbox Sizing”✅ Match hitbox to purpose:
- Small collectibles: 0.5 x 0.5
- Standard buttons: 1.0 x 1.0
- Doors/portals: 2.0 x 3.0
- Large zones: 3.0 x 3.0
✅ Test hitbox in-game:
- Use F3+B to see entity hitboxes
- Adjust based on player experience
Organization
Section titled “Organization”✅ Descriptive IDs:
journey:quest_triggerjourney:secret_coin_1journey:portal_to_nether✅ One file per interactable type:
interactables/ quest_starter.json secret_coin.json locked_portal.jsonIntegration with Other Systems
Section titled “Integration with Other Systems”With Tasks
Section titled “With Tasks”Start tasks from interactables:
q.player.start_task('journey:epic_quest');Check task completion for visibility:
q.player.has_completed_task('journey:prerequisite')With Flags
Section titled “With Flags”Track interaction state:
q.player.add_flag('button_pressed');!q.player.has_flag('collected') // Visibility checkWith Levelables
Section titled “With Levelables”Require skill levels:
q.player.levelable_level('Mining') >= 25.0Award XP:
q.player.progress_levelable('Exploration', 50.0);With Timelines
Section titled “With Timelines”Launch cinematic sequences:
q.player.launch_timeline('journey:cutscene_portal');With Zones
Section titled “With Zones”Show only in specific zones:
q.player.is_in_zone('secret-area-uuid')Storage Location
Section titled “Storage Location”Interactable Configs: config/journey/interactables/<name>.json
World Data: Saved automatically in world files
Limitations
Section titled “Limitations”What Interactables DO:
Section titled “What Interactables DO:”✅ Respond to right-click and left-click ✅ Execute MoLang scripts ✅ Have per-player conditional visibility ✅ Persist across server restarts ✅ Support customizable hitboxes
What Interactables DON’T Do:
Section titled “What Interactables DON’T Do:”❌ No visual model - Completely invisible (use particles for feedback) ❌ No animation - Static entities ❌ No movement - Fixed position ❌ No sound - Use MoLang to play sounds via commands
For visible NPCs: Use Cobblemon’s NPC system with TaskSource instead.
The Interactables System provides powerful invisible triggers and interactions for creating immersive, dynamic world content controlled entirely through MoLang scripting.