NPC Behaviors
This page explains the behavior-oriented Lua AI model used by Moongate v2 NPC brains.
Goal
Keep NPC AI maintainable by separating:
- brain orchestration (
brain_loop, event routing, priorities) - behaviors (small focused units like follow, evade, idle)
- runtime state (blackboard values stored per NPC)
Directory Layout
moongate_data/scripts/ai/
├── behavior.lua # behavior registry
├── lib/
│ └── utility_runner.lua # utility/priority behavior runner
├── behaviors/
│ ├── init.lua
│ ├── evade.lua
│ ├── follow.lua
│ └── idle.lua
└── brains/
├── guard.lua
└── utility_npc.lua
Brain Contract
Each brain table can expose:
brain_loop(npc_serial)required for coroutine executionon_event(event_type, from_serial, event_obj)optionalon_death(by_character, context)optional
In templates:
{
"id": "city_guard",
"brain": "guard"
}
"brain": "guard" resolves to Lua table guard.
Behavior Pattern
Behaviors are isolated modules registered by ID.
A behavior usually exposes:
score(npc_serial, ctx)to compute utilityrun(npc_serial, ctx)to execute action and return next delay (ms)on_event(npc_serial, ctx, event_type, from_serial, event_obj)optional
The utility runner selects the highest score, applies anti-jitter hold (min_hold_ms), then executes run.
Guard Brain Example
guard.lua uses three isolated behaviors:
evadefollowidle
At each tick:
- build context (
now_ms,min_hold_ms) - ask
utility_runnerfor the best behavior - execute behavior
coroutine.yield(delay_ms)
On speech events, the guard can set follow_target_serial in blackboard state.
State (Blackboard)
Behavior state is stored per NPC using npc_state module keys, for example:
follow_target_serialfollow_stop_rangeevade_desired_rangeevade_hp_threshold
This keeps behavior logic stateless and reusable.
Runtime Modules Used by Behaviors
Current core modules for behavior scripts:
perception(distance, nearby friend/enemy lookup, range checks)steering(follow/evade/wander/stop movement primitives)combat(targeting and swing hooks)npc_state(typed state variables)time,random,mobile(general runtime helpers)
Best Practices
- Keep each behavior focused on one decision.
- Store tunables in blackboard keys instead of hardcoding in multiple files.
- Use
on_eventfor reactive AI (speech, in-range, out-range), andbrain_loopfor tactical polling. - Return explicit delay values from behaviors to control tick frequency.