Lua 脚本:行为程序
行为程序用 Lua 行为取代怪物的原生 AI,由这些行为决定它往哪里移动、看向哪里、以哪个实体为目标以及何时攻击。EliteMobs 通过 MagmaCore 的 Mind 运行时,在 Minecraft 原生的 Brain 系统上运行这些程序。
行为程序不是 Lua 能力。Lua 能力对 on_boss_damaged_by_player 等 Boss 事件作出反应;行为程序则每个 tick 都会运行,并掌控怪物的移动控制。一个 Boss 可以同时使用两者:行为可以通过 on_mind_action 请求 Boss 的能力执行某个动作。
在自定义 Boss 文件中设置 behavior(参见创建 Boss),或在该实体类型的怪物属性文件中设置。behavior: native 保留原版 AI。如果服务器版本不支持原生 Mind,EliteMobs 会在启动时记录 Native Mind interface is unavailable on this Minecraft version.。此时选择了程序的自定义 Boss 不会生成,并记录 Cannot spawn <boss file>: ...。无论在哪个版本上,如果 Boss 的 behavior 指向缺失或无效的程序,也会以同样的方式失败。
文件
行为文件位于 plugins/EliteMobs/behaviors/。以下内置文件缺失时,EliteMobs 会写入它们:
plugins/
EliteMobs/
behaviors/
basic_melee.lua
modules/
target.lua
pursuit.lua
melee.lua
wander.lua
- 位于任何名为
modules的文件夹中的.lua文件是模块。其他所有.lua文件都是程序。 - Boss 通过相对于
behaviors/的路径引用程序,例如basic_melee.lua或guards/patrol_guard.lua。 - EliteMobs 会在其 Mind 服务启动时加载行为文件。未通过验证的文件会被跳过,控制台会记录
Could not load behavior <file>或Could not load behavior module <file>以及原因。 - 程序和模块 ID 使用
elitemobs命名空间,例如elitemobs:behavior/basic_melee。ID 为小写,可以包含a-z、0-9、.、_和-,冒号之后还可以包含/。两个程序不能共用同一个 ID,每个模块 ID 也应只由一个文件声明。 - 一个命名空间最多容纳 256 个模块。一个模块最多可以列出 32 个依赖,一个程序最多可以解析 64 个模块,每个源文件的长度限制为 1,000,000 个字符。
与其他 Lua 脚本相同的沙盒规则同样适用,参见 Lua 沙盒。每个运行程序的怪物都有自己的 Lua 环境,因此文件内的局部变量不会在怪物之间共享。
程序文件
程序文件返回 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
}
}
| 字段 | 类型 | 说明 |
|---|---|---|
id | 字符串 | 必填。带命名空间的 ID,behaviors/ 中的文件使用 elitemobs:...。 |
revision | 正整数 | 必填。 |
modules | 字符串数组 | 可选。要加入该程序的模块 ID,其记忆、传感器和行为都会并入程序。依赖会先于需要它们的模块加载。重复项和循环依赖会被拒绝。 |
memories、sensors、behaviors | 表 | 可选。程序可以用与模块相同的格式声明自己的这些内容。 |
budget | 表 | 可选的软性调度限制。参见预算。 |
runaway | 表 | 可选的单次回调硬性限制。参见预算。 |
模块文件
模块文件返回 ai.module { ... },用于组织可复用的记忆、传感器和行为:
| 字段 | 类型 | 说明 |
|---|---|---|
id | 字符串 | 必填。带命名空间的模块 ID。 |
revision | 正整数 | 必填。 |
dependencies | 字符串数组 | 可选。该模块需要的其他模块 ID。 |
memories | 表 | 可选。参见记忆。 |
sensors | 数组 | 可选。ai.sensor { ... } 条目。 |
behaviors | 数组 | 可选。ai.behavior { ... } 条目。 |
sensors、behaviors、modules 和 dependencies 必须是没有空位、也没有命名键的普通数组。传感器、行为和记忆的名称在一个程序的所有模块中必须唯一。
记忆
记忆是带类型的值,供同一只怪物的传感器和行为共享。按名称声明每一项:
memories = {
candidate = { type = 'uuid', persistent = false },
next_attack = 'integer'
}
| 类型 | Lua 值 |
|---|---|
string | 字符串 |
boolean | 布尔值 |
integer | 整数 |
number | 数字 |
uuid | UUID 字符串 |
position | { world = 'world', x = 0, y = 64, z = 0 } |
不含冒号的名称会被放入程序的命名空间。persistent 默认为 false;设为 true 会将该记忆标记为随 Mind 的已保存状态一起序列化。通过 c.memory:get(name)、c.memory:set(name, value, ttl_ticks)、c.memory:forget(name) 和 c.memory:contains(name) 读写记忆。set 的第三个参数是可选的;提供时,该值会在经过相应 tick 数后过期。使用未声明的名称会抛出错误。
传感器
传感器负责收集信息,通常写入记忆中。传感器在行为之前运行。
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
}
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id | 字符串 | 必填。 | |
interval | 正整数 | 1 | 两次运行之间间隔的 tick 数。 |
sense | 函数 | 必填。接收 Mind 上下文。 |
传感器不能使用 c.actuator;在传感器中调用执行器方法会抛出错误。
行为
行为在持有其声明的控制权期间对怪物施加作用。
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
}
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id | 字符串 | 必填。 | |
priority | 整数 | 0 | 数字越小,优先级越高。 |
controls | 数组 | 无 | 该行为运行期间租用的控制权。 |
can_start | 函数 | 始终为 true | 必须返回 true 或 false。 |
can_continue | 函数 | 与 can_start 相同 | 必须返回 true 或 false。 |
start | 函数 | 无 | 行为启动时运行一次。 |
tick | 函数 | 必填。行为运行期间每个 tick 运行一次。 | |
stop | 函数 | 无 | function(c, reason);行为停止时运行。 |
生命周期
- 停止状态下,行为会检查
can_start。当它返回true且该行为能够租用所有声明的控制权时,start就会运行。 - 运行期间的每个 tick(包括启动的那个 tick),都会先运行
can_continue。它返回false时,行为以原因completed停止;否则运行tick。 stop(c, reason)会收到completed、preempted、program_replaced、entity_removed、callback_failed或handle_closed之一。停止会释放该行为的控制权,并中止它所掌控的移动、目标或攻击。
can_continue、start 或 tick 中的 Lua 错误会使行为以 callback_failed 停止。can_start 或 can_continue 返回布尔值以外的任何内容都属于错误。每次失败时,控制台都会记录 Mind <program> callback <callback> failed with ...。
控制权
| 控制权 | 允许 |
|---|---|
ai.controls.move | c.actuator:move_to(...)、c.actuator:stop_moving() |
ai.controls.look | c.actuator:look_at(...) |
ai.controls.jump | c.actuator:jump() |
ai.controls.target | c.actuator:set_target(...)、c.actuator:clear_target() |
ai.controls.attack | c.actuator:attack(...) |
ai.controls.action | c.actions:request(...) |
ai.controls.use_item | 保留;Lua 执行器没有物品相关的方法。 |
每项控制权同一时间只属于一个正在运行的行为。priority 数字较小的行为会从数字较大的行为手中夺取控制权,后者以原因 preempted 停止。它无法夺取数字更小的行为所持有的控制权。优先级相同时,id 按字母顺序排在前面的行为获得控制权。在未持有对应控制权的情况下调用执行器方法会抛出错误。c.actuator:stop_all() 只会停止该行为所持有的内容。
在内置模块中,目标选择使用优先级 5,近战攻击使用 10,追击使用 20,空闲游荡使用 50,因此一旦出现目标,追击就会立即从游荡手中接管移动控制权。
请求 Boss 动作
持有 ai.controls.action 的行为可以调用 c.actions:request(identifier, payload)。EliteMobs 会通过 on_mind_action 将请求发送给 Boss 的 Lua 能力。identifier 必须是不超过 128 个字符的小写命名空间键,例如 'elitemobs:slam'。payload 最多包含 16 个条目,键为小写且不超过 64 个字符,字符串值限制为 256 个字符;无效的 identifier 或 payload 会抛出错误。该调用返回 accepted、deferred 或 rejected;在一个 tick 内超出程序 max_action_requests 的请求会返回 deferred。payload 格式请参阅 Mind 动作上下文。
预算
budget 设置软性调度限制。当某只怪物在一个 tick 内达到其中任一限制时,Mind 运行时会跳过该怪物在本 tick 剩余的回调,直到下一个 tick。已经在运行的回调总会执行完毕。
| 键 | 默认值 | 说明 |
|---|---|---|
callback_micros | 2000 | 运行时间超过此值的回调会结束该怪物在本 tick 的回调。 |
entity_micros | 4000 | 每只怪物每 tick 的回调总时间。 |
server_micros | 20000 | 当所有 Mind 程序合计已用掉这么多时间时,该怪物在本 tick 的回调就会停止。 |
max_callbacks | 128 | 每只怪物每 tick 的回调次数。 |
max_action_requests | 8 | 每只怪物每 tick 的动作请求次数;最多 64。 |
所有值都必须为正数,并且 callback_micros 不能超过 entity_micros,entity_micros 不能超过 server_micros。
runaway 设置会中断单个回调的硬性限制:
| 键 | 默认值 | 说明 |
|---|---|---|
cpu_micros | 50000 | 每个回调的线程 CPU 时间。 |
max_instructions | 50000 | 每个回调的 Lua 指令数。 |
budget 也接受 max_instructions,就像内置的 basic_melee.lua 那样。请在 budget 或 runaway 中声明它,不要两处都声明。超出失控限制的回调会像其他任何回调错误一样失败。
后续步骤
- 创建 Boss:behavior —— 为 Boss 选择程序
- Lua API 参考:Mind 程序上下文 —— 所有
c.memory、c.perception、c.actuator和c.actions方法 - 钩子与生命周期 ——
on_mind_action以及其他 Lua 能力钩子
