Skip to content

State Machines

State machines define per-phase AI behavior using states, transitions, and MoLang conditions. Each phase can have completely different AI patterns.

graph LR
    A[patrol_state] -->|target_close| B[aggressive_state]
    B -->|time_expired| A
    B -->|took_damage| C[retreat_state]
    C -->|distance_safe| A

    style A fill:#4a9eff
    style B fill:#e74c3c
    style C fill:#f39c12

A state machine consists of:

  • States: AI behaviors with movement controllers
  • Transitions: Conditional switches between states
  • Global Transitions: Emergency transitions from any state
{
"stateMachine": {
"initialState": "patrol",
"states": {
"patrol": {
"movementController": "titan:ground_patrol",
"speedMultiplier": 1.0,
"aggressionMultiplier": 1.0,
"minDuration": 2000,
"maxDuration": 5000,
"transitions": [
{
"targetState": "aggressive",
"condition": "q.titan_distance_to_target < 8.0"
}
]
},
"aggressive": {
"movementController": "titan:ground_berserker",
"speedMultiplier": 1.3,
"aggressionMultiplier": 1.8,
"minDuration": 1500,
"maxDuration": 4000,
"transitions": [
{
"targetState": "patrol",
"condition": "q.titan_time_in_state > 3500"
}
]
}
},
"globalTransitions": [
{
"targetState": "retreat",
"condition": "q.titan_time_since_damage < 1000"
}
]
}
}

Defines how the titan moves in this state:

ControllerBehaviorBest For
titan:ground_patrolStandard ground movementDefault/neutral state
titan:ground_berserkerAggressive ground rushMelee phase
titan:aerial_circlerCircle around target in airFlying bosses
titan:aerial_bomberDive bomb from aboveAggressive flying
titan:aerial_rushFast aerial chargesFlying pursuit
"movementController": "titan:ground_patrol"

Movement speed multiplier for this state:

"speedMultiplier": 1.0 // Normal speed
"speedMultiplier": 1.3 // 30% faster
"speedMultiplier": 0.7 // 30% slower

aggressionMultiplier (Optional, default: 1.0)

Section titled “aggressionMultiplier (Optional, default: 1.0)”

How aggressively titan pursues target:

"aggressionMultiplier": 1.0 // Normal
"aggressionMultiplier": 1.5 // More aggressive
"aggressionMultiplier": 0.5 // Defensive

Higher values = closer positioning, more frequent attacks.

Time range in milliseconds before forced transition:

"minDuration": 2000, // At least 2 seconds
"maxDuration": 5000 // At most 5 seconds

After maxDuration, titan MUST transition (first valid transition chosen).

MoLang executed when entering state:

"onEnter": "q.particle_effect('minecraft:flame', q.position(0), 50, 2.0); q.play_sound('minecraft:entity.blaze.ambient', 0.6, 1.2)"

Common uses:

  • Particle effects
  • Sound effects
  • Temporary buffs
  • Visual indicators

MoLang executed every tick while in state:

"onTick": "q.particle_effect('minecraft:flame', q.position(0), 2, 1.0)"

Common uses:

  • Particle trails
  • Periodic effects
  • Status checks

MoLang executed when leaving state:

"onExit": "q.particle_effect('minecraft:large_smoke', q.position(0), 30, 1.5)"

Common uses:

  • Cleanup effects
  • Remove temporary buffs
  • Transition particles

Store custom data accessible in MoLang:

"properties": {
"attack_power": 1.5,
"is_enraged": true,
"custom_flag": "value"
}

Access with: q.titan_state_property('attack_power')

{
"targetState": "aggressive",
"condition": "q.titan_distance_to_target < 8.0 && q.titan_time_in_state > 2000",
"priority": 0,
"cooldown": 0,
"onTransition": null
}

State ID to transition to:

"targetState": "aggressive"

Must match a key in the states object.

MoLang expression that must be true:

// Simple
"condition": "q.titan_time_in_state > 3000"
// Complex
"condition": "q.titan_distance_to_target < 5.0 && q.titan_health_percent < 0.5"
// Random
"condition": "q.titan_random < 0.3 && q.titan_time_in_state > 2000"

See MoLang Reference for all functions.

Higher priority transitions evaluated first:

// Checked first
{
"targetState": "enrage",
"condition": "q.titan_health_percent < 0.15",
"priority": 10
}
// Checked second
{
"targetState": "aggressive",
"condition": "q.titan_distance_to_target < 8.0",
"priority": 5
}
// Checked last
{
"targetState": "patrol",
"condition": "q.titan_time_in_state > 5000",
"priority": 0
}

Milliseconds before transition can be used again:

"cooldown": 5000 // 5 second cooldown

Prevents rapid state switching.

MoLang executed when transition occurs:

"onTransition": "q.play_sound('minecraft:entity.enderman.scream', 1.0, 1.0); q.particle_effect('minecraft:portal', q.position(0), 40, 2.0)"

Transitions that work from ANY state.

"globalTransitions": [
{
"targetState": "retreat",
"condition": "q.titan_time_since_damage < 1000",
"priority": 100
},
{
"targetState": "enrage",
"condition": "q.titan_health_percent < 0.1",
"priority": 90
}
]

Use for:

  • Emergency behaviors (low HP, took damage)
  • Phase-wide mechanics
  • Special triggers
FunctionReturnsDescription
q.titan_health_percent0.0-1.0Current HP percentage
q.titan_distance_to_targetFloatDistance to target (blocks)
q.titan_time_in_stateLongMilliseconds in current state
q.titan_time_since_damageLongMilliseconds since last hit
q.titan_aggression_levelFloatCurrent aggression level
FunctionReturnsDescription
q.titan_can_fly0.0/1.01.0 if Pokemon can fly
q.titan_melee_move_countIntMoves with range ≤ 5
q.titan_ranged_move_countIntMoves with range > 5
FunctionReturnsDescription
q.titan_current_phaseIntPhase index (0, 1, 2…)
q.titan_state_property(key)Number/BoolGet custom property
q.titan_previous_state(state)0.0/1.01.0 if previous state matches
FunctionReturnsDescription
q.titan_random0.0-1.0Random value
{
"stateMachine": {
"initialState": "circling",
"states": {
"circling": {
"movementController": "titan:aerial_circler",
"speedMultiplier": 1.1,
"aggressionMultiplier": 1.0,
"minDuration": 2000,
"maxDuration": 6000,
"onEnter": "q.particle_effect('minecraft:cloud', q.position(0), 30, 1.5)",
"onTick": "q.particle_effect('minecraft:cloud', q.position(0), 1, 0.5)",
"transitions": [
{
"targetState": "dive_bomb",
"condition": "q.titan_distance_to_target < 10.0 && q.titan_time_in_state > 2500",
"priority": 5
},
{
"targetState": "high_altitude",
"condition": "q.titan_random < 0.2 && q.titan_time_in_state > 3000",
"priority": 3
}
]
},
"dive_bomb": {
"movementController": "titan:aerial_bomber",
"speedMultiplier": 1.4,
"aggressionMultiplier": 1.8,
"minDuration": 1500,
"maxDuration": 4000,
"onEnter": "q.play_sound('minecraft:entity.ender_dragon.flap', 0.8, 1.2)",
"transitions": [
{
"targetState": "circling",
"condition": "q.titan_time_in_state > 3000",
"priority": 5
},
{
"targetState": "retreat",
"condition": "q.titan_time_since_damage < 1200",
"priority": 10
}
]
},
"high_altitude": {
"movementController": "titan:aerial_rush",
"speedMultiplier": 1.6,
"aggressionMultiplier": 0.3,
"minDuration": 2000,
"maxDuration": 5000,
"onEnter": "q.particle_effect('minecraft:firework', q.position(0), 50, 2.0)",
"transitions": [
{
"targetState": "dive_bomb",
"condition": "q.titan_time_in_state > 4000",
"priority": 5
}
]
},
"retreat": {
"movementController": "titan:aerial_rush",
"speedMultiplier": 1.8,
"aggressionMultiplier": 0.5,
"minDuration": 1000,
"maxDuration": 3000,
"onEnter": "q.particle_effect('minecraft:large_smoke', q.position(0), 60, 2.0)",
"transitions": [
{
"targetState": "circling",
"condition": "q.titan_distance_to_target > 15.0 || q.titan_time_in_state > 2500",
"priority": 5
}
]
}
},
"globalTransitions": [
{
"targetState": "retreat",
"condition": "q.titan_health_percent < 0.3 && q.titan_time_since_damage < 800",
"priority": 100
}
]
}
}
{
"stateMachine": {
"initialState": "patrol",
"states": {
"patrol": {
"movementController": "titan:ground_patrol",
"speedMultiplier": 1.0,
"aggressionMultiplier": 1.2,
"minDuration": 2000,
"maxDuration": 5000,
"onTick": "q.particle_effect('minecraft:smoke', q.position(0), 1, 0.3)",
"transitions": [
{
"targetState": "berserk",
"condition": "q.titan_distance_to_target < 8.0 && q.titan_time_in_state > 2000",
"priority": 5
}
]
},
"berserk": {
"movementController": "titan:ground_berserker",
"speedMultiplier": 1.5,
"aggressionMultiplier": 2.5,
"minDuration": 1500,
"maxDuration": 4500,
"onEnter": "q.play_sound('minecraft:entity.ravager.roar', 1.0, 0.8); q.particle_effect('minecraft:angry_villager', q.position(0), 20, 1.5)",
"onTick": "q.particle_effect('minecraft:flame', q.position(0), 2, 1.0)",
"transitions": [
{
"targetState": "patrol",
"condition": "q.titan_distance_to_target > 12.0 || q.titan_time_in_state > 4000",
"priority": 5
}
]
}
},
"globalTransitions": []
}
}
{
"stateMachine": {
"initialState": "neutral",
"states": {
"neutral": {
"movementController": "titan:ground_patrol",
"speedMultiplier": 1.0,
"aggressionMultiplier": 1.0,
"minDuration": 1000,
"maxDuration": 999999,
"transitions": [
{
"targetState": "ranged",
"condition": "q.titan_distance_to_target > 10.0",
"priority": 5
},
{
"targetState": "melee",
"condition": "q.titan_distance_to_target < 6.0",
"priority": 5
}
]
},
"ranged": {
"movementController": "titan:ground_patrol",
"speedMultiplier": 1.1,
"aggressionMultiplier": 0.8,
"minDuration": 2000,
"maxDuration": 6000,
"transitions": [
{
"targetState": "approach",
"condition": "q.titan_time_in_state > 4000",
"priority": 3
},
{
"targetState": "melee",
"condition": "q.titan_distance_to_target < 6.0",
"priority": 8
}
]
},
"approach": {
"movementController": "titan:ground_berserker",
"speedMultiplier": 1.3,
"aggressionMultiplier": 1.5,
"minDuration": 1000,
"maxDuration": 4000,
"transitions": [
{
"targetState": "melee",
"condition": "q.titan_distance_to_target < 6.0",
"priority": 8
},
{
"targetState": "neutral",
"condition": "q.titan_time_in_state > 3500",
"priority": 3
}
]
},
"melee": {
"movementController": "titan:ground_berserker",
"speedMultiplier": 1.2,
"aggressionMultiplier": 2.0,
"minDuration": 1500,
"maxDuration": 5000,
"transitions": [
{
"targetState": "neutral",
"condition": "q.titan_distance_to_target > 10.0 || q.titan_time_in_state > 4500",
"priority": 5
}
]
}
},
"globalTransitions": []
}
}

Alternate between attack and retreat:

aggressive (2-4s) → defensive (1-3s) → aggressive

Good for melee bosses.

Change behavior based on range:

ranged (>10 blocks) → approach (6-10) → melee (<6)

Good for mixed-range bosses.

Add unpredictability:

state_a → (30% chance) state_b
state_a → (70% chance) state_c

Use q.titan_random < 0.3 for 30% chance.

Burst then retreat:

neutral → aggressive (max 5s) → cooldown → neutral

Prevents endless aggression.

  • Check condition MoLang syntax
  • Verify target state exists
  • Check maxDuration isn’t too short
  • Test condition independently: q.titan_distance_to_target in chat
  • No valid transitions found
  • Add fallback transition with q.titan_time_in_state > X
  • Check global transitions don’t override everything
  • Add cooldown to transitions
  • Increase minDuration
  • Make conditions mutually exclusive
  • Verify controller ID is correct
  • Check if Pokemon can actually fly (for aerial controllers)
  • Ensure movement isn’t stopped by other systems

Build complex boss AI with state machines! 🤖