Skip to main content

Lua Scripting: Behavior Programs

webapp_banner.jpg

A behavior program replaces a mob's native AI with Lua behaviors that decide where it moves, what it looks at, which entity it targets and when it attacks. EliteMobs runs these programs on Minecraft's native Brain system through MagmaCore's Mind runtime.

Behavior programs are not Lua powers. A Lua power reacts to boss events such as on_boss_damaged_by_player; a behavior program runs every tick and owns the mob's movement controls. A boss can use both: a behavior can ask the boss's powers to perform an action through on_mind_action.

Selecting a program

Set behavior in a custom boss file, as described in Creating Bosses, or in the entity type's mob-properties file. behavior: native keeps vanilla AI. If the server version has no native Mind support, EliteMobs logs Native Mind interface is unavailable on this Minecraft version. at startup. A custom boss that selects a program then does not spawn and logs Cannot spawn <boss file>: .... A boss whose behavior names a missing or invalid program fails the same way on any version.


Files​

Behavior files live in plugins/EliteMobs/behaviors/. EliteMobs writes these bundled files when they are missing:

plugins/
EliteMobs/
behaviors/
basic_melee.lua
modules/
target.lua
pursuit.lua
melee.lua
wander.lua
  • A .lua file inside any folder named modules is a module. Every other .lua file is a program.
  • A boss references a program by its path relative to behaviors/, for example basic_melee.lua or guards/patrol_guard.lua.
  • EliteMobs loads behavior files when its Mind service starts. A file that fails validation is skipped and the console logs Could not load behavior <file> or Could not load behavior module <file> with the reason.
  • Program and module ids use the elitemobs namespace, such as elitemobs:behavior/basic_melee. Ids are lowercase and may contain a-z, 0-9, ., _ and -, plus / after the colon. Two programs cannot share an id, and each module id should be declared by one file.
  • One namespace holds at most 256 modules. A module can list at most 32 dependencies, a program can resolve at most 64 modules, and each source file is limited to 1,000,000 characters.

The same sandbox rules as other Lua scripts apply. See the Lua Sandbox. Each mob that runs a program gets its own Lua environment, so file-local variables are not shared between mobs.


Program File​

A program file returns ai.program { ... }:

return ai.program {
id = 'elitemobs:behavior/basic_melee', revision = 1,
modules = {
'elitemobs:behavior/target', 'elitemobs:behavior/pursuit',
'elitemobs:behavior/melee', 'elitemobs:behavior/wander'
},
budget = {
callback_micros = 2000, entity_micros = 4000, server_micros = 5000,
max_callbacks = 20, max_instructions = 12000, max_action_requests = 2
}
}
FieldTypeNotes
idstringRequired. Namespaced id, elitemobs:... for files in behaviors/.
revisionpositive integerRequired.
modulesarray of stringsOptional. Module ids whose memories, sensors and behaviors join the program. Dependencies load before the modules that need them. Duplicates and dependency cycles are rejected.
memories, sensors, behaviorstablesOptional. A program can declare its own, in the same format as a module.
budgettableOptional soft scheduling limits. See Budgets.
runawaytableOptional hard per-callback limits. See Budgets.

Module File​

A module file returns ai.module { ... } and groups reusable memories, sensors and behaviors:

FieldTypeNotes
idstringRequired. Namespaced module id.
revisionpositive integerRequired.
dependenciesarray of stringsOptional. Other module ids this module needs.
memoriestableOptional. See Memories.
sensorsarrayOptional. ai.sensor { ... } entries.
behaviorsarrayOptional. ai.behavior { ... } entries.

sensors, behaviors, modules and dependencies must be plain arrays without gaps or named keys. Sensor, behavior and memory names must be unique across every module in a program.


Memories​

Memories are typed values that sensors and behaviors share for one mob. Declare each one by name:

memories = {
candidate = { type = 'uuid', persistent = false },
next_attack = 'integer'
}
TypeLua value
stringstring
booleanboolean
integerwhole number
numbernumber
uuidUUID string
position{ world = 'world', x = 0, y = 64, z = 0 }

A name without a colon is placed in the program's namespace. persistent defaults to false; true marks the memory for serialization with the Mind's saved state. Read and write memories through c.memory:get(name), c.memory:set(name, value, ttl_ticks), c.memory:forget(name) and c.memory:contains(name). The third set argument is optional; when supplied, the value expires after that many ticks. Using an undeclared name raises an error.


Sensors​

A sensor gathers information, usually into memories. Sensors run before behaviors.

ai.sensor {
id = 'elitemobs:behavior/find_target', interval = 20,
sense = function(c)
local target = c.perception:nearest_player(35)
if target then c.memory:set('candidate', target.uuid, 21)
else c.memory:forget('candidate') end
end
}
FieldTypeDefaultNotes
idstringRequired.
intervalpositive integer1Ticks between runs.
sensefunctionRequired. Receives the Mind context.

Sensors cannot use c.actuator; calling an actuator method from a sensor raises an error.


Behaviors​

A behavior acts on the mob while it holds the controls it declares.

ai.behavior {
id = 'elitemobs:behavior/attack', priority = 10,
controls = { ai.controls.attack },
can_start = function(c) return c.perception:current_target() ~= nil end,
can_continue = function(c) return c.perception:current_target() ~= nil end,
tick = function(c)
local target = c.perception:current_target()
if target then c.actuator:attack(target) end
end
}
FieldTypeDefaultNotes
idstringRequired.
priorityinteger0Lower numbers take precedence.
controlsarraynoneControls this behavior leases while it runs.
can_startfunctionalways trueMust return true or false.
can_continuefunctionsame as can_startMust return true or false.
startfunctionnoneRuns once when the behavior starts.
tickfunctionRequired. Runs every tick while the behavior runs.
stopfunctionnonefunction(c, reason); runs when the behavior stops.

Lifecycle​

  1. While stopped, the behavior checks can_start. When it returns true and the behavior can lease every declared control, start runs.
  2. On every tick while it runs, including the tick it started, can_continue runs first. When it returns false, the behavior stops with reason completed; otherwise tick runs.
  3. stop(c, reason) receives one of completed, preempted, program_replaced, entity_removed, callback_failed or handle_closed. Stopping releases the behavior's controls and halts the movement, target or attack it held.

A Lua error in can_continue, start or tick stops the behavior with callback_failed. Returning anything other than a boolean from can_start or can_continue is an error. The console logs Mind <program> callback <callback> failed with ... for each failure.

Controls​

ControlAllows
ai.controls.movec.actuator:move_to(...), c.actuator:stop_moving()
ai.controls.lookc.actuator:look_at(...)
ai.controls.jumpc.actuator:jump()
ai.controls.targetc.actuator:set_target(...), c.actuator:clear_target()
ai.controls.attackc.actuator:attack(...)
ai.controls.actionc.actions:request(...)
ai.controls.use_itemReserved; the Lua actuator has no item method.

Each control belongs to one running behavior at a time. A behavior with a lower priority number takes a control from a behavior with a higher number, which stops with reason preempted. It cannot take a control held by a behavior with a lower number. Between equal priorities, the behavior whose id sorts first alphabetically wins the control. Calling an actuator method without holding its control raises an error. c.actuator:stop_all() stops only what the behavior holds.

In the bundled modules, target selection uses priority 5, melee attacks 10, pursuit 20 and idle wandering 50, so pursuit takes movement from wandering as soon as a target exists.

Requesting boss actions​

A behavior holding ai.controls.action can call c.actions:request(identifier, payload). EliteMobs sends the request to the boss's Lua powers through on_mind_action. The identifier must be a lowercase namespaced key of at most 128 characters, such as 'elitemobs:slam'. The payload holds at most 16 entries with lowercase keys of at most 64 characters, and string values are limited to 256 characters; an invalid identifier or payload raises an error. The call returns accepted, deferred or rejected; requests beyond the program's max_action_requests in one tick return deferred. See the Mind action context for the payload format.


Budgets​

budget sets soft scheduling limits. When a mob reaches one in a tick, the Mind runtime skips that mob's remaining callbacks until the next tick. A callback that is already running always finishes.

KeyDefaultNotes
callback_micros2000A callback that runs longer than this ends the mob's callbacks for the tick.
entity_micros4000Total callback time per mob per tick.
server_micros20000The mob's callbacks stop for the tick once all Mind programs together have used this much time.
max_callbacks128Callbacks per mob per tick.
max_action_requests8Action requests per mob per tick; at most 64.

All values must be positive, and callback_micros cannot exceed entity_micros, which cannot exceed server_micros.

runaway sets hard limits that interrupt a single callback:

KeyDefaultNotes
cpu_micros50000Thread CPU time per callback.
max_instructions50000Lua instructions per callback.

budget also accepts max_instructions, as in the bundled basic_melee.lua. Declare it in budget or runaway, not both. A callback that exceeds a runaway limit fails like any other callback error.


Next Steps​