Skip to main content
← Back

Cobblemon Ambient NPC Behaviours

Three "atmosphere" behaviours for Cobblemon NPCs, plus an invisible Ambient preset that can easily uplift any scene. On their own, NPCs stand still and silent, so my goal for these behaviours was to add subtle sound and visual effects that make a setting feel more lively. Each one is added to an NPC, toggled on, and configured from the in-game editor (/npcedit) without writing any scripts.

Ambient Sound Loop

Plays an ambient sound on a configured interval, broadcast from the NPC's position so players nearby hear it with proper directional falloff. Up to three sound ids can be configured (ambient_sound_id_1 through _3), and each fire picks one non-blank slot at random. Volume and pitch are each rolled per fire between a configurable min and max, so a repeated clip does not sound mechanical.

A few details that make it usable in practice:

  • Sound channel: ambient_sound_channel (default ambient) sets which volume slider the sound obeys, so an effect can route through block, music, or voice instead.
  • Interval jitter: the gap is ambient_sound_interval_ticks plus a random 0..ambient_sound_jitter_ticks. As such, two identical NPCs placed together drift out of sync instead of chorusing.
  • Activation radius: the sound broadcasts to players within the engine's fixed 16-block radius, so ambient_sound_player_radius is an activation gate rather than the audible distance. It defaults to 16 to keep the gate aligned with who can actually hear it.

Idle Animation Cycler

Periodically plays a weighted random idle animation (wave, look_around, stretch, yawn, or whatever the model defines) so an NPC that is standing around does something every so often. Up to five animation slots are configurable, each with its own pick weight (idle_anim_weight_N), and each fire runs a cumulative weighted roll over the non-blank slots. The gap is a random value between idle_anim_interval_min_ticks and idle_anim_interval_max_ticks.

A busy gate keeps the cycler out of the way of anything more important. It checks three cheap conditions before firing and skips the cycle if any is true:

  • q.entity.has_walk_target: the NPC is walking somewhere.
  • q.entity.is_in_dialogue(): a player is talking to it.
  • q.entity.is_in_battle(): it is mid-battle.

As such, this behaviour composes well with moves_to_players, stays_at_current_position, and the Dialogue Behaviours, so the NPC tracks the player, emotes between conversations, and goes still the moment someone interacts.

Ambient Particle Emitter

Emits a bedrock (snowstorm) particle effect on a configurable interval from a point near the NPC. Up to three particle ids can be configured (ambient_particles_id_1 through _3), one picked at random each fire. The emission point is an offset (ambient_particles_x/y/z) from the NPC's current position, so the cloud follows a moving NPC, and the default y = 1.0 lifts particles off the feet to around body height.

The underlying spawn call takes a single point, with no native count or spread, so two parameters are layered on top in Molang:

  • Count: ambient_particles_count spawns that many instances per fire in a loop.
  • Spread: each instance is scattered by a random value within ±ambient_particles_dx/dy/dz, so a single point becomes a box-shaped cloud. Every instance in one fire shares the same picked particle id.

Shared Features

All three share the same tick-and-emit skeleton, which is what keeps them cheap on a populated server:

  • Player activation radius: the tick does real work only while a player is within the configured radius. An unvisited NPC costs one data read and one compare per tick, so a map full of ambient emitters stays light until someone walks up.
  • Restart-safe cadence: the next-fire time is persisted on the entity (q.entity.data.ambient_sound_next_tick and the equivalents) as a monotonic game_time value, so the schedule survives a server restart.
  • Pause condition: each exposes an optional Molang gate (*_paused_condition) that skips a cycle when it assigns v.result = true. Using this gate you could make an effect pause depending on the time of day, weather, or even the state of other NPCs.
  • Back-off when idle: when nobody is in range, or the pause gate trips, the next check is scheduled about a second out instead of every tick.

The Ambient preset

The Ambient preset is an invisible NPC designed as a drop-in solution to uplift a scene with these behaviours. It uses the cobblemon:puzzle resource with the invis variation, hides its nametag, disables gravity, and ships with both ambient_sound_loop and ambient_particle_emitter already applied (alongside core, set_entity_properties, a small quarter_cube_hitbox, and a no-op interaction). It does not despawn.

The point is a marker a server owner can drop anywhere, enable the sound and/or particle channels on, and configure in place. Because the NPC is invisible and stationary, it layers onto an existing build without adding a model.

The Idle Animation Cycler is left off the preset, since an invisible NPC has no animations to play. It is meant to be added to a visible NPC directly, such as a Pokemon or Simple Dialogue NPC.

Techy Stuff

  • Built on Cobblemon's behaviour and Molang systems, declared in JSON and dispatched through run_script tasks on the minecraft:idle activity.
  • Each behaviour pairs a *_tick script with an undo script, so toggling a variable re-applies cleanly on save and removing the behaviour tears down its state.
  • Random slot selection uses math.random_integer. The cycler's weighting is a sum-and-roll over the per-slot weights, with a float-edge fallback guarding the exact upper boundary.
  • Sound and particle ids and animation names run through the shared replace_placeholders_var helper, so {{npc}} resolves the same way it does in the animation-interaction and dialogue behaviours.
  • The Ambient preset is a single NPC JSON (apply_behaviours) that composes the existing emitter behaviours, so it inherits any fixes or variables they gain later.

Built for the Cobblewilds Cobblemon server, but available for commission.