Lua 脚本:NPC 脚本
EliteMobs NPC Lua 脚本是独立的 .lua 文件,可以挂接到 NPC 配置上。它们与 Boss Lua 能力是分开的:Boss 能力位于 plugins/EliteMobs/powers/,而 NPC 脚本位于 plugins/EliteMobs/npc_scripts/。
NPC 脚本现在与 Boss 能力、FreeMinecraftModels 道具以及 FMM 物品运行在同一个统一的 MagmaCore 脚本运行时上。这意味着 NPC 脚本可以获得完整的共享脚本接口 —— context.world(包括 strike_lightning)、context.zones、context.scheduler、context.cooldowns、context.log、context.event 和 context.player —— 外加一个 NPC 专属的 context.npc 表。凡是 MagmaCore 向脚本暴露的内容,在这里同样可用。
NPC Lua 脚本仍处于实验阶段。NPC 专属的钩子以及 context.npc 辅助方法可能会发生变化。共享表(context.world、context.zones、context.scheduler、context.cooldowns、context.log、context.event、context.player)与脚本引擎和 Lua API 参考中记录的是同一批。
文件位置
在以下位置创建 NPC 脚本文件:
plugins/
EliteMobs/
npc_scripts/
wave.lua
子文件夹会被递归扫描。但脚本是仅按文件名注册的,因此 npc_scripts/wave.lua 与 npc_scripts/town/wave.lua 会发生冲突 —— 请让整个目录树中的文件基名保持唯一。
在 NPC 配置中,.lua 扩展名是可选的:- wave 和 - wave.lua 都会解析为 wave.lua。如果 NPC 配置引用了一个不存在的脚本,EliteMobs 会记录一条警告,NPC 仍然会正常生成。
将脚本挂接到 NPC
在 NPC 配置中添加一个 scripts: 列表:
scripts:
- wave.lua
可以为一个 NPC 挂接多个脚本:
scripts:
- wave.lua
- greeting_particles.lua
脚本按优先级顺序运行。priority 值越小越先运行。如果省略优先级,则默认为 0。
脚本结构
每个 NPC 脚本都必须返回一个表:
return {
api_version = 1,
priority = 0,
on_spawn = function(context)
context.state.spawned = true
context.npc:play_model_animation("idle")
end
}
只接受以下这些顶层字段:
| 字段 | 类型 | 说明 |
|---|---|---|
api_version | number | 必填。必须为 1。 |
priority | number | 可选。值越小越先运行。 |
on_spawn | function | 在 NPC 生成之后运行。 |
on_remove | function | 在 NPC 被移除时运行。 |
on_game_tick | function | 在 NPC 有效期间的每个服务器 tick 运行。请保持它非常轻量。 |
on_npc_interact | function | 玩家与该 NPC 交互时运行。 |
on_npc_proximity_enter | function | 玩家进入该 NPC 的激活半径时运行一次。 |
on_npc_proximity_leave | function | 玩家离开该 NPC 的激活半径时运行一次。 |
on_zone_enter | function | 玩家进入该脚本正在监视的区域时运行(参见 context.zones)。 |
on_zone_leave | function | 玩家离开被监视的区域时运行。 |
区域监视只跟踪玩家 —— 怪物和其他实体永远不会触发 on_zone_enter / on_zone_leave。
未知的顶层键会在脚本加载期间被拒绝。辅助函数应当在返回的表之上声明为 local 函数。
邻近钩子
NPC 邻近钩子使用 NPC 配置中的 activationRadius 值。
| 钩子 | 触发时机 |
|---|---|
on_npc_proximity_enter | 玩家从 NPC 激活半径之外移动到半径之内。 |
on_npc_proximity_leave | 玩家从 NPC 激活半径之内移动到半径之外。 |
这些钩子由服务端的邻近扫描器按 NPC、按玩家分别跟踪。站在某个 NPC 附近不会阻止另一个 NPC 触发它自己的进入事件,而持续待在半径之内也不会反复刷屏式地触发进入事件。
正常的问候、对话与任务指示行为仍会照常运行。Lua 钩子是在这些行为之上叠加的。
共享脚本接口
由于 NPC 脚本运行在统一运行时上,每个 NPC 钩子也会收到 FreeMinecraftModels 脚本所使用的那些共享 MagmaCore 上下文表。EliteMobs Boss 能力运行在同一个运行时上,但其中若干表使用的是 Boss 专属的变体。完整的方法列表请参阅 Lua API 参考与脚本引擎:
| 表 | 作用 |
|---|---|
context.world | 世界效果与查询:strike_lightning、spawn_particle、play_sound、set_block_at、place_temporary_block、spawn_entity、spawn_firework、get_nearby_entities、get_nearby_players、raycast 等等。坐标形式(strike_lightning(x, y, z))与位置表形式(strike_lightning_at_location(loc))都被接受。 |
context.zones | 创建空间区域(create_sphere(x, y, z, radius)、create_cylinder(x, y, z, radius, height)、create_cuboid(x, y, z, xSize, ySize, zSize))—— 每个都会返回一个数字句柄。watch(handle, on_enter, on_leave) 开始跟踪(这些回调触发的是你的 on_zone_enter / on_zone_leave 钩子,而不是你传入的那些函数);unwatch(handle) 停止跟踪。 |
context.scheduler | run_later(ticks, fn)、run_repeating(delay, interval, fn)、cancel(task_id)。 |
context.cooldowns | 共享的 MagmaCore 冷却:local_ready、local_remaining、check_local、set_local、global_ready、set_global。 |
context.log | info(msg)、warn(msg)、error(msg) —— 写入服务器控制台。 |
context.event | 当前的 Bukkit 事件(存在时)。参见下文。 |
context.player | 触发交互的玩家(存在时)。参见下文。 |
context.state | 一个普通的 Lua 表,在该 NPC 被移除之前,对这个 NPC 脚本实例始终持续存在。 |
示例:交互时降下雷击
return {
api_version = 1,
on_npc_interact = function(context)
-- NPC scripts can now reach the full world API.
context.world:strike_lightning_at_location(context.npc:get_location())
end
}
context.npc
context.npc 在每个 NPC 钩子中都可用。
字段
| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 配置中的 NPC 显示名称。 |
filename | string | NPC 配置文件名。 |
uuid | string | 运行时的 NPC UUID。 |
activation_radius | number | 配置的激活半径。 |
current_location | location table | 当支撑实体存在时的位置快照。 |
entity_type | string | 当支撑实体存在时的 Bukkit 实体类型。 |
方法
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
is_valid() | - | boolean | NPC 是否仍然拥有一个有效的支撑实体。 |
get_location() | - | location table | 当前 NPC 位置;若实体不可用,则返回生成位置。 |
get_eye_location() | - | location table | 当前的视线位置,兜底为生成位置。 |
get_activation_radius() | - | number | 当前配置的激活半径。 |
get_nearby_players(radius) | number | table | NPC 半径范围内的玩家包装对象。 |
face_direction_or_location(target) | vector or location | nil | 朝向一个方向向量,或转向某个位置/玩家位置。 |
say_greeting(player?) | player、UUID、名称或 nil | nil | 发送配置好的问候语。可用时默认发给触发的玩家。 |
say_dialog(player?) | player、UUID、名称或 nil | nil | 发送配置好的对话。可用时默认发给触发的玩家。 |
say_farewell(player?) | player、UUID、名称或 nil | nil | 发送配置好的告别文本。可用时默认发给触发的玩家。 |
play_model_animation(name) | string | nil | 若存在自定义模型动画则播放它。否则是安全的空操作。 |
patrol_pause() | - | boolean | 暂停已配置的巡逻。 |
patrol_resume() | - | boolean | 取消脚本等待或临时移动并恢复巡逻。 |
walk_to(x, y, z) | 三个数字 | boolean | 走到相对于原点的偏移,然后恢复巡逻。长距离会自动求解。 |
hold(x, y, z) | 三个数字 | boolean | 走到偏移位置并停留。 |
teleport(x, y, z) | 三个数字 | boolean | 目标区域进行实体 tick 时传送。 |
若 NPC 没有配置巡逻,或请求无法被接受,移动方法会返回 false。请参阅 NPC 与首领巡逻。
context.player
context.player 在 on_npc_interact、on_npc_proximity_enter 和 on_npc_proximity_leave 中可用。在不涉及玩家的生命周期钩子中,它为 nil。
它就是共享的 MagmaCore 玩家包装对象 —— 与 Boss 能力和 FMM 脚本使用的是同一个完整的生物/玩家表,因此它暴露的内容远不止基础项(生命值、药水效果、send_message、show_title、show_action_bar、get_held_item、射线检测等等)。完整列表请参阅 Lua API 参考。这里常用的有:
| 字段 / 方法 | 说明 |
|---|---|
name | 玩家名称。 |
uuid | 玩家 UUID。 |
current_location | 玩家当前位置表;这是字段,不是方法。 |
get_eye_location() | 当前玩家视线位置。 |
send_message(text) | 发送一条聊天消息。支持颜色代码。 |
在共享的辅助函数中使用 context.player 之前,请始终进行 nil 检查。
entity_type 是小写的在共享的 MagmaCore 实体表上,entity_type 是 Bukkit 名称的小写形式("player"、"zombie")。只有 context.npc.entity_type 和 EliteMobs 的 Boss 能力实体表使用大写形式。如果一个脚本必须同时兼容两者,请进行不区分大小写的比较。
EliteMobs 向每一张共享实体表添加的字段
只要 EliteMobs 在运行,它就会向每一张 MagmaCore 实体表补充额外的字段 —— 包括 context.player、context.npc:get_nearby_players(...) 返回的包装对象,以及 FreeMinecraftModels 道具与物品脚本所看到的那些:
| 字段 | 类型 | 说明 |
|---|---|---|
is_elite | boolean | 若 EliteMobs 将该实体作为精英追踪则为 true |
is_custom_boss | boolean | 若它是自定义 Boss 则为 true(当 is_elite 为 false 时始终为 false) |
is_significant_boss | boolean | 对于生命值倍率大于 1 的自定义 Boss 为 true —— 实用的"这是一个真正的 Boss,而不是增援"判断 |
elite | table 或 nil | 仅存在于精英上。见下文 |
elite 子表:
| 字段 / 方法 | 类型 | 说明 |
|---|---|---|
elite.level | number | 精英等级 |
elite.name | string 或 nil | 精英显示名称 |
elite.health | number | 精英当前生命值(实时读取) |
elite.max_health | number | 精英最大生命值(实时读取) |
elite.is_custom_boss | boolean | 与顶层同名字段的值相同 |
elite.health_multiplier | number | 配置的生命值倍率 |
elite.damage_multiplier | number | 配置的伤害倍率 |
elite:remove() | — | 让该精英消失 |
-- Warn the approaching player if a real boss is loose near this NPC
on_npc_proximity_enter = function(context)
if context.player == nil then return end
local here = context.npc:get_location()
local nearby = context.world:get_nearby_entities(here.x, here.y, here.z, 40)
for i = 1, #nearby do
if nearby[i].is_significant_boss then
context.player:send_message("&cA boss is nearby: " .. tostring(nearby[i].elite.name))
return
end
end
end
这些字段不会出现在 EliteMobs 的 Boss 能力实体包装对象上,后者由独立的 Boss 端表构建器生成 —— 那一套请参阅 Boss 与实体。
context.event
当钩子没有对应的 Bukkit 事件时,context.event 为 nil。存在时,它就是共享的 MagmaCore 事件表:
| 字段 / 方法 | 说明 |
|---|---|
is_cancelled | 底层事件是否已被取消(仅对可取消事件有意义)。 |
cancel() | 在事件可取消时取消它。 |
uncancel() | 在事件可取消时撤销取消。 |
player | 事件的触发者(例如进行交互的玩家),以玩家包装对象的形式给出(存在时)。 |
对于交互/邻近触发的玩家,请优先使用 context.player(这些钩子中它一定会被设置)。
状态、调度器与冷却
context.state 是一个普通的 Lua 表,在该 NPC 被移除之前,对这个 NPC 脚本实例始终持续存在。
context.scheduler 是共享的 MagmaCore 调度器。MagmaCore 的名称和 EliteMobs 的 run_after / run_every 名称都可以使用 —— 它们是同一行为的别名:
| 方法 | 参数 | 说明 |
|---|---|---|
run_later(ticks, callback) / run_after(ticks, callback) | number, function | 延迟之后运行一次。返回一个任务 ID。 |
run_repeating(delay, interval, callback) | number, number, function | 在初始延迟之后重复运行。返回一个任务 ID。 |
run_every(interval, callback) | number, function | 每 interval ticks 运行一次(初始延迟为 0)。返回一个任务 ID。 |
cancel(task_id) / cancel_task(task_id) | number | 取消一个自有任务。 |
调度器回调会收到一个全新的 context。它们不会收到原始的 context.player 或 context.event。当 NPC 被移除时,所有自有任务都会被自动取消。
context.cooldowns 是共享的 MagmaCore 冷却表:
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
local_ready(key?) | string | boolean | 本地冷却结束时返回 true。 |
local_remaining(key?) | string | number | 剩余的 ticks,就绪时为 0。 |
check_local(key?, duration) | string, number | boolean | 若就绪,则开始冷却并返回 true。 |
set_local(duration, key?) | number, string | nil | 设置或重置冷却。 |
global_ready() | - | boolean | 共享的全局冷却就绪时返回 true。 |
set_global(duration) | number | nil | 启动全局冷却。 |
NPC 脚本现在使用共享的 MagmaCore 冷却参数顺序(check_local(key?, duration)),与 Boss 能力和 FreeMinecraftModels 脚本一致。更早的实验性 NPC 版本使用的是 check_local(duration, key?) —— 请把旧脚本更新为共享的参数顺序。
示例:靠近时挥手
这个脚本让 NPC 面向进入范围的玩家,并播放 wave 自定义模型动画。只要玩家一直停留在半径之内,邻近进入事件对每个 NPC/玩家组合已经只会触发一次;这里的冷却是为了避免快速的离开/再进入循环导致动画过于频繁地重播。
return {
api_version = 1,
priority = 0,
on_npc_proximity_enter = function(context)
if context.player == nil then return end
if context.cooldowns:check_local("wave:" .. context.player.uuid, 60) then
context.npc:face_direction_or_location(context.player.current_location)
context.npc:play_model_animation("wave")
end
end
}
当 NPC 没有自定义模型,或该模型没有对应动画时,play_model_animation(name) 会安全地什么都不做。
性能建议
- 让
on_game_tick钩子保持精简。对每个定义了它的 NPC 脚本实例,它每秒会运行 20 次。(没有声明on_game_tick的脚本永远不会被 tick。) - 对于邻近相关的行为,请优先使用
on_npc_proximity_enter和on_npc_proximity_leave,而不是每 tick 轮询附近玩家。 - 使用
context.cooldowns:check_local(...)来限制动画、音效和粒子爆发的频率。 - 当某个行为不需要每 tick 执行时,使用
context.scheduler:run_repeating(...)并设置一个合理的间隔。 - 避免在 Lua 中进行大范围搜索。
context.npc:get_nearby_players(radius)用于小范围的局部检查没有问题,但大范围扫描应当留在插件运行时中完成。
相关页面
- 创建 NPC —— NPC 配置字段,包括
activationRadius - Lua 入门指南 —— Boss Lua 能力
- 脚本引擎 —— 共享的 Lua 概念与统一运行时
- Lua API 参考 ——
context.world、context.player、context.zones等的完整方法列表
