跳到主要内容

Lua 脚本:行为程序

webapp_banner.jpg

行为程序用 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数字
uuidUUID 字符串
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);行为停止时运行。

生命周期​

  1. 停止状态下,行为会检查 can_start。当它返回 true 且该行为能够租用所有声明的控制权时,start 就会运行。
  2. 运行期间的每个 tick(包括启动的那个 tick),都会先运行 can_continue。它返回 false 时,行为以原因 completed 停止;否则运行 tick。
  3. 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.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_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_micros2000运行时间超过此值的回调会结束该怪物在本 tick 的回调。
entity_micros4000每只怪物每 tick 的回调总时间。
server_micros20000当所有 Mind 程序合计已用掉这么多时间时,该怪物在本 tick 的回调就会停止。
max_callbacks128每只怪物每 tick 的回调次数。
max_action_requests8每只怪物每 tick 的动作请求次数;最多 64。

所有值都必须为正数,并且 callback_micros 不能超过 entity_micros,entity_micros 不能超过 server_micros。

runaway 设置会中断单个回调的硬性限制:

键默认值说明
cpu_micros50000每个回调的线程 CPU 时间。
max_instructions50000每个回调的 Lua 指令数。

budget 也接受 max_instructions,就像内置的 basic_melee.lua 那样。请在 budget 或 runaway 中声明它,不要两处都声明。超出失控限制的回调会像其他任何回调错误一样失败。


后续步骤​