Behaviors

From Citizens Wiki

Revision as of 18:55, 1 December 2025 by Fullwall (talk | contribs) (Behaviors)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Behaviors

The Behaviors system allows NPCs to run AI behaviors defined in YAML format. Behaviors can be simple actions like walking or waiting, or complex sequences with conditions, loops, and parallel execution.

Tree Structure

All behavior trees start with a tree: root node:


Code:
tree:
  sequence:
    - behavior_name arg1 arg2
    - another_behavior

Built-in Types

sequence

Runs children in order until one fails.


Code:
tree:
  sequence:
    - say "First"
    - wait 1s
    - say "Second"

selector / random

Runs one sub-behavior at random.

Code:
tree:
  selector:
    - sequence:
        - if `player_nearby`:
            - say "I see you!"
    - wander radius=15

parallel

Runs multiple behaviors at the same time.


Code:
tree:
  parallel:
    - wander radius=10 pathfind=true
    - sequence:
        - wait 5s
        - say "Still wandering..."

loop [condition]

Runs the children in sequence if the condition is true.


Code:
tree:
  loop `memory.get('counter', 0) < 5`:
    - say "Count is {memory.get('counter', 0)}"
    - set counter `memory.get('counter', 0) + 1`
    - wait 1s

if [condition] / else

Code:
tree:
  sequence:
    - if `memory.has('greeted')`:
        - say "Welcome back!"
      else:
        - say "Nice to meet you!"
        - set greeted true

invert

Inverts the success or failure of its child.


Code:
tree:
  invert:
    - fail

timeout [duration]

If the child doesn't complete within the specified duration, it fails.


Code:
tree:
  timeout 5s:
    - walkto 500 64 500

eval [language]

Evaluates multiple expressions.


Code:
tree:
  eval:
    - memory.set('x', 100)
    - memory.set('y', 64)
    - memory.set('z', 200)

Or with a specific language (javascript, molang):


Code:
tree:
  eval molang:
    - v.test = 5
    - v.result = v.test * 2

Movement

walkto

Walks to specified coordinates.

Inline usage:

Code:
- walkto 100 64 200 speed=1.5 range=50

Named parameters:

Code:
- walkto:
    x: 100
    y: 64
    z: 200
    speed: 1.5
    range: 50
    distance_margin: 2

Parameters:

  • x, y, z - Target coordinates (required)
  • speed - Movement speed multiplier (optional)
  • range - Maximum pathfinding range (optional)
  • distance_margin / margin - How close in blocks to get to target (optional)

wander

Random wandering within a radius.


Code:
- wander radius=10 pathfind=true

Parameters:

  • radius - Wander radius in blocks (default: 10)
  • pathfind - Whether to use pathfinding (default: true)

look

Makes the NPC look at specific coordinates.


Code:
- look 100 64 200

Parameters:

  • x, y, z - Target coordinates (required)

teleport

Teleports the NPC to coordinates.


Code:
- teleport 100 64 200
- teleport 100 64 200 world_name

Parameters:

  • x, y, z - Target coordinates (required)
  • world - World name (optional, defaults to current world)

Timing

wait

Waits for a specified duration.


Code:
- wait 3s      # 3 seconds
- wait 100t    # 100 ticks
- wait 2m      # 2 minutes

Duration formats:

  • s - seconds
  • t - ticks
  • m - minutes
  • Can also use expressions: wait `some_expression`

wait_until

Waits until a condition becomes true.


Code:
- wait_until `player.distance < 10`

cooldown

Rate limits behaviors. Only succeeds if enough time has passed since the last time it was run.


Code:
- cooldown greeting 30s
- say "Hello there!"

Parameters:

  • key - Cooldown identifier (required)
  • duration - Cooldown duration (default: 1s)

Memory Management

set

Sets a memory variable.


Code:
- set counter 5
- set name "Bob"
- set calculated `10 * 5`

Parameters:

  • key - Variable name (required)
  • value - Value to set (required, can be expression)

forget

Removes a memory variable.


Code:
- forget counter

clear_memory

Clears all memory variables.


Code:
- clear_memory

Communication

say

Makes the NPC say a message to nearby players.


Code:
- say "Hello, world!"
- say `"Player count: " + player.count`

emit_signal

Emits a signal.


Code:
- emit_signal "start_patrol"

emit_signal_to

Emits a signal to a specific NPC by ID or UUID.


Code:
- emit_signal_to 123 "alert"
- emit_signal_to "uuid-string" "alert"

Parameters:

  • npc - NPC ID or UUID (required)
  • signal - Signal name (required)

emit_global_signal

Emits a signal that all NPCs receive.


Code:
- emit_global_signal "server_restart_warning"

wait_for_signal

Waits until a signal is received.


Code:
- wait_for_signal "start_patrol"
- say "Signal received!"

World Interaction

break_block

Breaks a block realistically using the NPC's held item at the specified coordinates.


Code:
- break_block 100 64 200 radius=3

Parameters:

  • x, y, z - Block coordinates (required)
  • radius - Maximum distance in blocks to break from (default: 3)

Control Flow

succeed

Always returns SUCCESS.


Code:
- succeed

fail

Always returns FAILURE.


Code:
- fail


repeat

Repeats a specified number of times.


Code:
- repeat 5:
    - say "Counting..."
    - wait 1s

Parameters:

  • count - Number of repetitions (default: 1)

Special Syntax

Command Execution

Lines starting with / are executed as commands:


Code:
tree:
  sequence:
    - /npc speak "Hello from command!"
    - /say This is a server command

Expression Evaluation

Lines starting with backtick (`) are evaluated as expressions:


Code:
tree:
  sequence:
    - `inv.add(item.json('{"material":"DIAMOND","amount":64}'))`
    - `memory.set('flag', true)`

Expression System

The behavior tree system integrates with the Expression Registry for dynamic values.

Expression Syntax

Expressions can be used in several ways:

Inline in conditions:

Code:
- if `memory.get('counter') < 10`:
    - say "Counter is low"

In parameter values:

Code:
- walkto `npc.location.x + 10` `npc.location.y` `npc.location.z`

In text:

Code:
- say "Counter value: {memory.get('counter')}"

Standalone evaluation:

Code:
- `memory.set('result', 10 * 5)`

Memory System

The memory system provides persistent storage across behavior executions.

Usage

Code:
tree:
  sequence:
    - set visited_waypoint_1 true
    - set visit_count `memory.get('visit_count', 0) + 1`
    - if `memory.has('greeted')`:
        - say "Welcome back! Visit #{memory.get('visit_count')}"
      else:
        - say "First visit!"
        - set greeted true
    - forget temporary_flag

Memory Methods

  • memory.set(key, value) - Set a value
  • memory.get(key, default) - Get a value with optional default
  • memory.has(key) - Check if key exists
  • memory.remove(key) - Remove a key
  • memory.clear() - Clear all memory

Memory is automatically saved and loaded with the NPC.

Complete Examples

Basic Patrol

Code:
tree:
  sequence:
    - set patrol_started true
    - loop `memory.get('patrol_count', 0) < 3`:
        - walkto `npc.location.x + 10` `npc.location.y` `npc.location.z`
        - wait 2s
        - cooldown speak 10s
        - say "Patrolling area {memory.get('patrol_count', 0)}"
        - walkto `npc.location.x - 10` `npc.location.y` `npc.location.z`
        - set patrol_count `memory.get('patrol_count', 0) + 1`
    - say "Patrol complete!"
    - forget patrol_count

Signal Coordination

Guard NPC:

Code:
tree:
  sequence:
    - wait_for_signal "intruder_alert"
    - say "Intruder detected! Responding!"
    - walkto `signal.data.x` `signal.data.y` `signal.data.z`
    - emit_global_signal "area_secured"

Lookout NPC:

Code:
tree:
  sequence:
    - if `player.distance < 10`:
        - cooldown alert 60s
        - emit_global_signal "intruder_alert"
        - say "Alert! Intruder spotted!"

Conditional Greeter

Code:
tree:
  sequence:
    - if `memory.has('greeted')`:
        - say "Welcome back!"
      else:
        - sequence:
            - say "Nice to meet you!"
            - set greeted true
    - cooldown greet 10s
    - wait 2s
    - say "Have a nice day!"

Resource Gathering

Code:
tree:
  sequence:
    - set gathered 0
    - loop `memory.get('gathered', 0) < 5`:
        - wander radius=20 pathfind=true
        - wait 5s
        - break_block `npc.location.x` `npc.location.y - 1` `npc.location.z`
        - set gathered `memory.get('gathered', 0) + 1`
        - say "Gathered {memory.get('gathered')} resources"
    - say "Resource gathering complete!"
    - teleport 0 64 0

Loading Behavior Trees

External File

You can load trees from external files in the behaviors/ directory.


Code:
traits:
  - name: behavior
    tree: patrol_behavior.yml

The behaviors will be loaded from plugins/Citizens/behaviors/patrol_behavior.yml

Template System

Behavior trees can be included in templates:


Code:
greeter:
  traits:
    - name: behavior
      tree:
        sequence:
          - cooldown greet 10s
          - say "Hello there, traveler!"
          - wait 2s

API

Registering Custom Behaviors

Register custom behaviors with the BehaviorRegistry:


Code:
registry.registerBehavior("my_behavior", (params, context) -> {
    String arg = context.getArgOrParam(0, "param_name", params, "default");
    ExpressionValue argHolder = registry.getExpressionRegistry().parseValue(arg);

    return new InstantBehavior() {
        @Override
        public void reset() {
        }

        @Override
        public BehaviorStatus run() {
            NPC npc = context.getNPC();
            // Your behavior logic here
            return BehaviorStatus.SUCCESS;
        }

        @Override
        public boolean shouldExecute() {
            return true;
        }
    };
});

Custom Expression Languages

Register custom expression engines with the ExpressionRegistry for domain-specific languages.