Skip to content

NPCs & Path System

Journey extends Cobblemon’s NPC system with powerful MoLang scripting functions and an automated path walking system. Create dynamic NPCs with animations, particles, messages, and automated patrol routes.

  1. Overview
  2. NPC MoLang Functions
  3. Path Walking System
  4. Path Configuration
  5. NPC Path Assignments
  6. Path Visualization
  7. Complete Examples

NPC Scripting Extensions:

  • Animation control via MoLang
  • Particle effects (Cobblemon Snowstorm)
  • Chat messages and dialogue
  • Distance calculations
  • Path walking control functions

Path Walking System:

  • Waypoint-based automated movement
  • Script execution at each node
  • Looping and one-time paths
  • Speed control and timing
  • Progress persistence across restarts
  • Visual path preview with particles

Journey does NOT include:

  • NPC creation/spawning system (use Cobblemon’s NPC system)
  • Dialogue tree editor (use Cobblemon’s dialogue system)
  • NPC AI/behavior trees
  • NPC trading systems
  • Quest giver GUIs
  • Relationship/reputation systems

All NPC management is done through Cobblemon. Journey provides scripting extensions and movement automation.

Reference: Cobblemon NPC Documentation


These functions are available in NPC contexts (Cobblemon NPC dialogues, interactions). All functions are called on the npc object.

npc.play_animation(animation_name, [player_uuid])

Section titled “npc.play_animation(animation_name, [player_uuid])”

Plays an animation on the NPC.

Parameters:

  • animation_name (string): The name of the animation to play
  • player_uuid (string, optional): UUID of specific player to show animation to. If not provided, shows to all nearby players.

Returns: 1.0 on success, 0.0 on failure

Examples:

# Play animation for all nearby players
npc.play_animation('wave')
# Play animation for specific player only
npc.play_animation('point', 'player-uuid-here')

npc.snowstorm_entity_particle(particle_id, locator, [player_uuid])

Section titled “npc.snowstorm_entity_particle(particle_id, locator, [player_uuid])”

Spawns a Cobblemon Snowstorm particle effect attached to the NPC.

Parameters:

  • particle_id (string): Resource location of the particle effect
  • locator (string): Locator name on the NPC model
  • player_uuid (string, optional): UUID of specific player to show particle to

Examples:

# Show particle to all players
npc.snowstorm_entity_particle('cobblemon:quest_exclamation', 'head')
# Show particle to specific player
npc.snowstorm_entity_particle('cobblemon:dialogue_dots', 'chest', 'player-uuid')

npc.snowstorm_particle(particle_id, x, y, z, [player_uuid])

Section titled “npc.snowstorm_particle(particle_id, x, y, z, [player_uuid])”

Spawns a particle effect at a specific position relative to the NPC.

Parameters:

  • particle_id (string): Resource location of the particle effect
  • x, y, z (number): Relative position offsets
  • player_uuid (string, optional): UUID of specific player to show particle to

Examples:

# Spawn particle 2 blocks above NPC for all players
npc.snowstorm_particle('cobblemon:sparkles', 0, 2, 0)
# Spawn particle for specific player
npc.snowstorm_particle('cobblemon:marker', 1, 1, 0, 'player-uuid')

Makes the NPC say a message in chat.

Parameters:

  • message (string): The message text. Use %npc% as placeholder for NPC name.
  • player_uuid (string, optional): UUID of specific player to send message to. If not provided, sends to all nearby players (within 64 blocks).

Examples:

# Message to all nearby players
npc.say('Hello there! I am %npc%!')
# Message to specific player
npc.say('Welcome back, trainer!', 'player-uuid')

Gets the distance between the NPC and a specific player.

Parameters:

  • player_uuid (string): UUID of the player

Returns: Distance in blocks (number)

Examples:

# Check if player is within 5 blocks
npc.distance_to_player('player-uuid') < 5.0 ? 1.0 : 0.0
# Get exact distance
temp.dist = npc.distance_to_player('player-uuid');

Journey includes a comprehensive automated NPC movement system based on waypoints.

Starts the NPC walking along a predefined path.

Parameters:

  • path_id (string): The ID of the path to walk

Returns: 1.0 on success, 0.0 on failure

Example:

npc.walk_path('village_patrol_route')

Stops the NPC from walking its current path.

Returns: 1.0 on success, 0.0 on failure

Example:

npc.stop_walking()

Pauses the NPC’s path walking (can be resumed later).

Returns: 1.0 on success, 0.0 on failure

Example:

npc.pause_walking()

Resumes a paused path walk.

Returns: 1.0 on success, 0.0 on failure

Example:

npc.resume_walking()

Checks if the NPC is currently walking a path.

Returns: 1.0 if walking, 0.0 if not

Example:

# Check if NPC is walking
npc.is_walking_path() == 1.0 ? 'walking' : 'idle'

Gets the NPC’s progress along the current path.

Returns: Progress percentage (0.0-100.0)

Example:

# Check if halfway through path
npc.walking_progress() >= 50.0 ? 1.0 : 0.0

Assigns a path to the NPC that persists across restarts.

Parameters:

  • path_id (string): The ID of the path to assign
  • auto_start (number, optional): 1.0 to auto-start (default), 0.0 to not auto-start

Returns: 1.0 on success

Examples:

# Assign path with auto-start
npc.assign_path('daily_route')
# Assign path without auto-start
npc.assign_path('event_route', 0.0)

Unassigns the current path from the NPC.

Returns: 1.0 on success

Example:

npc.unassign_path()

Checks if the NPC has an assigned path.

Returns: 1.0 if has assigned path, 0.0 if not

Example:

npc.has_assigned_path() == 1.0 ? 'patrolling' : 'stationary'

Gets the ID of the NPC’s assigned path.

Returns: Path ID string, or empty string if none assigned

Example:

# Store path ID
temp.current_path = npc.get_assigned_path();

Paths are defined in JSON configuration files. Each path consists of waypoints (nodes) with positions, look directions, and optional scripts.

File Location: config/journey/paths/<name>.json

{
"id": "example_path",
"name": "Example Path",
"nodes": [
{
"position": {
"x": 213.0,
"y": 63.0,
"z": -104.0
},
"look_target": {
"x": 105.0,
"y": 64.0,
"z": 100.0
},
"script": ""
},
{
"position": {
"x": 213.0,
"y": 63.0,
"z": -112.0
},
"script": "q.wait(20);"
}
],
"loop": true,
"speed_multiplier": 0.5,
"description": "An example path demonstrating the JSON format"
}
FieldTypeRequiredDefaultDescription
idStringYes-Unique path identifier
nameStringYes-Human-readable display name
nodesArrayYes-List of PathNode objects (minimum 1)
loopBooleanNofalseWhether path repeats from beginning
speed_multiplierFloatNo1.0Movement speed modifier (must be positive)
descriptionStringNo""Optional description text

Each node represents a waypoint with position, look direction, and optional script.

{
"position": {
"x": 100.0,
"y": 64.0,
"z": -200.0
},
"look_target": {
"x": 110.0,
"y": 64.0,
"z": -200.0
},
"script": "q.wait_seconds(3);"
}
FieldTypeRequiredDescription
positionVector3YesWorld coordinates where NPC walks to
position.xFloatYesX coordinate
position.yFloatYesY coordinate
position.zFloatYesZ coordinate
look_targetVector3NoCoordinates NPC faces when reaching node
look_target.xFloatNoX coordinate to look at
look_target.yFloatNoY coordinate to look at
look_target.zFloatNoZ coordinate to look at
scriptStringNoMoLang expression to execute at this node

If look_target is omitted: NPC will look in the direction of the next node (or current position if last node).


Scripts execute when the NPC reaches a node. Use MoLang expressions for timing and actions.

Wait for ticks:

q.wait(20); // Wait 20 ticks (1 second)

Wait for seconds:

q.wait_seconds(3); // Wait 3 seconds (60 ticks)

Check if waiting:

q.is_waiting(); // Returns 1.0 if NPC is waiting, 0.0 otherwise

Get remaining wait time:

q.wait_remaining(); // Returns remaining ticks

Examples:

Pause for 5 seconds:

{
"position": {"x": 100, "y": 64, "z": 200},
"script": "q.wait_seconds(5);"
}

Quick pause (0.5 seconds):

{
"position": {"x": 100, "y": 64, "z": 200},
"script": "q.wait(10);"
}

No pause (move immediately):

{
"position": {"x": 100, "y": 64, "z": 200},
"script": ""
}

Assign paths to NPCs in the global assignment file.

File Location: config/journey/npc_paths.json

[
{
"npc_uuid": "0af627fe-3254-48b0-9892-881a42512c43",
"assigned_path_id": "example_path",
"auto_start": true,
"current_index": 0,
"is_complete": false,
"is_paused": false,
"reach_threshold": 0.9
}
]
FieldTypeRequiredDefaultDescription
npc_uuidUUID StringYes-UUID of the NPC entity
assigned_path_idStringYes-ID of the path to follow
auto_startBooleanNotrueAutomatically start path when NPC loads
current_indexIntegerNo0Current node index (for progress persistence)
is_completeBooleanNofalseWhether path has been completed
is_pausedBooleanNofalseWhether path walker is paused
reach_thresholdFloatNo0.9Distance (blocks) to consider node “reached”

The reach_threshold determines how close the NPC must get to a node before moving to the next:

{
"reach_threshold": 0.9 // NPC must be within 0.9 blocks
}

Recommendations:

  • Precise positioning: 0.2 - 0.5
  • Standard movement: 0.9 (default)
  • Forgiving movement: 1.5 - 2.0

Adjust movement speed for the entire path:

{
"speed_multiplier": 1.5 // 50% faster
}

Examples:

  • 0.5 - Half speed (slow, cautious)
  • 1.0 - Normal speed (default)
  • 1.5 - 50% faster
  • 2.0 - Double speed

Looping Path:

{
"loop": true
}
  • After reaching the last node, returns to first node
  • Continues indefinitely
  • Perfect for patrols, circular routes

One-Time Path:

{
"loop": false
}
  • After reaching the last node, stops moving
  • Remains at final position
  • Perfect for delivery routes, one-way journeys

The system automatically handles stuck NPCs:

Detection:

  • If NPC moves < 0.05 blocks for 60 ticks (3 seconds), considered stuck

Recovery:

  • If close to target (within 2.5x threshold): Force progression to next node
  • If far from target: Retarget with 1.5x speed boost and apply gentle push force

Corrective Movement:

  • Applied when NPC is close but not quite at target
  • Gentle push force (0.02 × speed multiplier) toward target
  • Prevents NPCs from circling waypoints

NPC path progress saves automatically:

When Progress is Saved:

  • Every 10 seconds (200 ticks) during normal operation
  • When path completes
  • When path is cancelled/paused
  • When NPC is removed or unloaded

What is Saved:

  • Current node index
  • Completion status
  • Pause state

On Server Restart:

  • NPCs resume from saved node index
  • Progress continues seamlessly

Players can preview paths in-game using particle effects.

Via Player Query Function:

q.player.toggle_path_visualization() // Toggle on/off
q.player.show_path_visualization(true) // Enable
q.player.show_path_visualization(false) // Disable
q.player.preview_path('guard_patrol') // Preview specific path

Via GUI:

/journey pathmanager

Opens a GUI for path creation and visualization control.

Path Lines:

  • END_ROD particles: Regular node-to-node connections
  • ENCHANT particles: Loop connection (last node → first node)
  • Particle spacing: 0.5 blocks apart

Node Markers:

  • Circular particle effect at each node
  • 8 particles in a 0.5-block radius circle
  • Animated vertical movement
  • Different colors based on node index

Look Direction Arrows:

  • Small arrow particles showing look_target direction
  • Only shown when look_target differs from node position

Rendering:

  • Maximum distance: 64 blocks from player
  • Update frequency: Every 10 ticks (0.5 seconds)
  • Per-player visibility (doesn’t affect other players)

# In Cobblemon dialogue script
npc.play_animation('greet');
npc.say('Welcome, %npc% here! Need any help?');
# Show exclamation if player has quest available
q.player.has_flag('quest_available') == 1.0 ?
npc.snowstorm_entity_particle('cobblemon:quest_ready_exclamation', 'head') :
0.0
# In dialogue condition
npc.distance_to_player(query.player.uuid) < 3.0 &&
q.player.has_completed_task('introduction_quest') == 0.0 ?
npc.say('Hello there! You look new around here.') :
0.0
# Start patrol during daytime only
query.world.is_day == 1.0 ?
npc.walk_path('day_patrol_route') :
npc.walk_path('night_patrol_route')
# Check walking progress and trigger event at midpoint
temp.progress = npc.walking_progress();
temp.progress >= 50.0 && temp.progress < 51.0 ?
npc.say('Halfway there!') :
0.0

File: config/journey/paths/guard_patrol.json

{
"id": "guard_patrol",
"name": "Guard Patrol Route",
"nodes": [
{
"position": {"x": 100, "y": 64, "z": 100},
"look_target": {"x": 100, "y": 64, "z": 110},
"script": "q.wait_seconds(2);"
},
{
"position": {"x": 120, "y": 64, "z": 100},
"look_target": {"x": 120, "y": 64, "z": 110},
"script": "q.wait_seconds(2);"
},
{
"position": {"x": 120, "y": 64, "z": 120},
"look_target": {"x": 110, "y": 64, "z": 120},
"script": "q.wait_seconds(2);"
},
{
"position": {"x": 100, "y": 64, "z": 120},
"look_target": {"x": 110, "y": 64, "z": 120},
"script": "q.wait_seconds(2);"
}
],
"loop": true,
"speed_multiplier": 0.8,
"description": "Rectangular patrol with 2-second pauses at each corner"
}

Behavior: Guard walks a rectangle, pausing 2 seconds at each corner, looking outward.

File: config/journey/paths/merchant_route.json

{
"id": "merchant_route",
"name": "Traveling Merchant Route",
"nodes": [
{
"position": {"x": 0, "y": 64, "z": 0},
"look_target": {"x": 5, "y": 64, "z": 0},
"script": "q.wait_seconds(30);"
},
{
"position": {"x": 100, "y": 64, "z": 0},
"script": ""
},
{
"position": {"x": 100, "y": 64, "z": 100},
"look_target": {"x": 105, "y": 64, "z": 105},
"script": "q.wait_seconds(30);"
},
{
"position": {"x": 0, "y": 64, "z": 100},
"script": ""
}
],
"loop": true,
"speed_multiplier": 0.6,
"description": "Merchant travels between two towns, staying 30 seconds at each"
}

Behavior: Merchant travels slowly between two locations, pausing for 30 seconds at each stop.

File: config/journey/paths/messenger_delivery.json

{
"id": "messenger_delivery",
"name": "Urgent Message Delivery",
"nodes": [
{
"position": {"x": 0, "y": 64, "z": 0},
"script": ""
},
{
"position": {"x": 50, "y": 64, "z": 50},
"script": "q.wait(10);"
},
{
"position": {"x": 100, "y": 64, "z": 0},
"look_target": {"x": 110, "y": 64, "z": 0},
"script": "q.wait_seconds(5);"
}
],
"loop": false,
"speed_multiplier": 1.5,
"description": "Fast one-way delivery from point A to point B"
}

Behavior: Messenger runs quickly from start to end, stops at destination.


/journey pathmanager

Opens interactive GUI for:

  • Creating paths visually
  • Editing existing paths
  • Toggling path visualization
  • Testing paths

Create interactables that control paths:

q.player.preview_path('merchant_route');
q.player.toggle_path_visualization();

Use NPC arrival as task objectives:

{
"event": "ENTITY_INTERACT",
"event_data": {
"uuid": "npc-uuid-here"
},
"filter": "q.entity.position.x >= 100.0 && q.entity.position.x <= 105.0",
"target": 1
}

Smooth corners:

// Add intermediate nodes for smooth turns
{"position": {"x": 100, "y": 64, "z": 100}},
{"position": {"x": 105, "y": 64, "z": 105}}, // Curve
{"position": {"x": 110, "y": 64, "z": 110}}

Appropriate node spacing:

  • Close nodes (2-5 blocks): Smooth, curved paths
  • Far nodes (10-20 blocks): Straight routes, faster travel

Test in-game:

  • Use visualization to preview
  • Watch NPC movement
  • Adjust reach_threshold if stuck

Realistic pauses:

// Guard looking around
"script": "q.wait_seconds(3);"
// Merchant selling
"script": "q.wait_seconds(30);"
// Quick checkpoint
"script": "q.wait(10);"

Match speed to purpose:

  • Patrols: 0.7 - 0.9
  • Normal walking: 1.0
  • Running/urgent: 1.3 - 1.8

Point NPCs at points of interest:

{
"position": {"x": 100, "y": 64, "z": 100},
"look_target": {"x": 95, "y": 64, "z": 110}, // Look at shop
"script": "q.wait_seconds(2);"
}

Face next waypoint for natural movement:

{
"position": {"x": 100, "y": 64, "z": 100},
"look_target": {"x": 120, "y": 64, "z": 100} // Next node direction
}

Descriptive path IDs and names:

{
"id": "town_guard_patrol_north",
"name": "North Gate Patrol Route"
}

Document complex paths:

{
"description": "Merchant travels from village to city, stopping at waypoint tavern for 1 minute"
}

  • Verify the animation name exists in the NPC’s model
  • Check that the NPC entity is loaded
  • Ensure player UUID is valid (if targeting specific player)
  • Verify the path JSON is valid and loaded
  • Check that the NPC entity can move (canMove: true)
  • Ensure path nodes are accessible (no walls/obstacles)
  • Verify NPC is not in dialogue or other interrupted state
  • Verify particle ID is correct and registered
  • Check that locator exists on NPC model (for entity particles)
  • Ensure player is within render distance
  • Check auto_start is true in npc_paths.json
  • NPC UUID is correct
  • Path ID matches exactly
  • At least one node exists in path
  • NPC is not paused (is_paused: false)
  • Increase reach_threshold (try 1.5 or 2.0)
  • Check for obstacles in path
  • Ensure Y coordinates match terrain height
  • Add intermediate nodes to avoid complex terrain
{
"speed_multiplier": 1.2 // Increase for faster, decrease for slower
}
{
"script": "q.wait_seconds(5);" // Must have semicolon
}
  • Player is within 64 blocks of path
  • Visualization is enabled for player
  • Path ID is correct in preview command

Paths are lightweight:

  • Minimal server impact
  • Efficient AI pathfinding
  • Cached node targets
  • Automatic stuck recovery

Optimization tips:

  • Use fewer nodes for simple paths (system handles straight lines)
  • Avoid excessive wait scripts on many NPCs
  • Limit visualization to when needed (not always-on)

The NPCs & Path System provides complete control over NPC scripting and movement with minimal configuration. Combine with other Journey systems for dynamic, living worlds with realistic NPC behavior.