Lua 脚本:故障排除
本页涵盖编写或调试 Lua 能力时可能遇到的常见问题,以及面向从 EliteScript 迁移而来的作者的迁移建议。如果你正在调试 NPC 脚本,请参阅 NPC 脚本。如果你正在寻找可用的示例,请参阅示例与模式。如果你刚刚起步,请参阅入门指南。
EliteMobs 使用在各个 Nightbreak 插件之间共享的 MagmaCore Lua 脚本引擎。关于沙盒、调度器、区域、世界 API、实体表以及玩家 UI 方法等共享概念的文档,请参阅 MagmaCore Lua 脚本引擎 页面。
常见问题
1. 能力完全无法加载
在服务器启动时检查服务器控制台是否有错误。最常见的原因是 Lua 语法错误(缺少 end、括号不匹配等)。还要确认文件以 .lua 结尾,并放置在正确的 powers 目录中。
2. 钩子从不触发
确认钩子名称的拼写与钩子列表中所列完全一致。常见错误:on_boss_hit(错误)与 on_boss_damaged_by_player(正确),或 on_tick(错误)与 on_game_tick(正确)。
3. context.player 为 nil
并非所有钩子都提供玩家。on_spawn、on_game_tick 和 on_exit_combat 没有玩家。on_enter_combat 确实提供 context.player(触发战斗的玩家)。在 on_boss_damaged(通用伤害)中,造成伤害者可能不是玩家。在使用 context.player 之前,始终要添加一个 nil 防护检查。
4. 超时 / 超出执行预算
如果某个钩子或回调耗时过长,该能力会被自动禁用以防止卡顿。控制台消息看起来像这样:
[Lua] my_power.lua took 73ms in 'on_game_tick' (limit: 50ms) — script disabled to prevent lag.
常见原因:遍历过多实体、每 tick 创建过多区域,或在 on_game_tick 中运行开销很大的字符串操作。将开销大的工作放在冷却门后,或减少每次调用所做的工作。
5. 调度器回调使用了过时的数据
你很可能使用了外层的 context,而非回调参数。将 function() ... context.boss ... end 改为 function(tick_context) ... tick_context.boss ... end。
6. 区域查询返回不到任何实体
仔细检查区域定义。对于原生区域,确保 kind 为小写("sphere",而非 "SPHERE")。对于脚本工具,确保 shape 为大写("CONE",而非 "cone")。还要确认 origin 或 Target 确实解析为一个有效的位置。
7. 粒子未出现
确认粒子名称是一个大写形式的有效 Bukkit Particle 枚举值。常见错误:"flame"(错误)与 "FLAME"(正确)。还要检查 amount 至少为 1,并且该位置位于已加载的区块中。
8. 冷却似乎不起作用
确保你使用的是 check_local(key, duration)(它会在一次调用中同时检查并设置),而不是先用 local_ready(key) 再单独使用 set_local(duration, key)。如果你单独使用 local_ready,你就只检查了冷却却从未设置它。
9. Boss 死亡后仍持续运行能力
在 on_exit_combat 和/或 on_death 中添加清理逻辑以取消调度器任务。如果 Boss 死亡,on_exit_combat 应当会触发,但在两个钩子中都添加显式清理会更安全。
阅读错误消息
当 Lua 能力中出现问题时,控制台会打印一个以 [Lua] 为前缀的友好错误块。这些消息会用通俗的英文准确告诉你是哪个文件、哪一行、哪个钩子,以及出了什么问题。在调试之前,请务必通读完整消息。
一个典型的错误看起来像这样:
[Lua] Error in 'push_zone.lua' at line 35 during 'on_boss_damaged_by_player':
[Lua] -> You tried to call a method or function that doesn't exist.
[Lua] -> Check the method name for typos, or make sure you're using ':' (colon) for method calls, not '.' (dot).
[Lua] -> Script has been disabled for this entity to prevent further errors.
该系统会将常见的 Lua 错误翻译成通俗的英文。以下是最常见的几种:
| 原始 Lua 错误 | 控制台告诉你的内容 |
|---|---|
attempt to call nil | 你尝试调用了一个不存在的方法或函数。检查方法名是否有拼写错误,或确保你在方法调用时使用的是 :(冒号)而非 .(点)。 |
index expected, got nil | 你尝试访问某个为 nil 的对象上的字段。检查前面的代码是否已对其进行初始化。 |
attempt to index | 你尝试访问某个 nil 或无效值上的属性。 |
bad argument | 显示具体的参数不匹配详情(期望类型与实际类型)。 |
| 超时(Timeout) | <filename> 在 'hook_name' 中耗时 X 毫秒(上限:50 毫秒)—— 脚本已被禁用以防止卡顿。 |
当你在控制台中看到 [Lua] 错误时,错误消息会用通俗的英文准确告诉你是哪个文件、哪一行、哪个钩子,以及出了什么问题。在深入代码之前,请通读完整消息——它通常会直接指向修复方法。
不要假设存在未记录的别名
Lua API 公开了一组特定的方法名。如果你是手动编写能力或在 AI 辅助下编写,请不要假设存在简写或替代名称。以下是一些不存在且会导致错误的名称示例:
show_temporary_boss_bar()—— 改用player:show_boss_bar(title, color, style, duration)。run_command_as_player()—— 改用player:run_command(command)。em.location(...)—— 方法名错误。改用em.create_location(x, y, z),或context.boss:get_location()/context.player.current_location。em.vector(...)—— 方法名错误。改用em.create_vector(x, y, z),或纯{x=0, y=1, z=0}表。em.zone.sphere(...)—— 方法名错误。改用em.zone.create_sphere_zone(radius),或一个区域定义表,如{kind = "sphere", radius = 5, origin = location}。entity:teleport_to(...)—— 改用entity:teleport_to_location(location)。entity:set_velocity(...)—— 改用entity:set_velocity_vector(vector)。entity:set_facing(...)—— 改用entity:face_direction_or_location(direction_or_location)。
如有疑问,请查阅 API 参考页面(Boss 与实体、世界与环境、区域与目标选取)。如果那里没有记录,它就不存在。
给 EliteScript 作者的迁移建议
如果你已经能写出不错的 EliteScript,那么学习 Lua 能力的最简单方法是:
-
继续以事件、目标、区域、相对向量和粒子的方式来思考。 概念是相同的——只是语法变了。EliteScript 事件变成了诸如
on_spawn或on_boss_damaged_by_player之类的钩子名称。目标和区域作为表传递给context.script,使用的字段名与 EliteScript 区域 和 EliteScript 目标 页面中记录的相同。 -
把你的控制流移到 Lua 中。 随机掷骰、共享辅助函数、循环、持久状态(
context.state)以及任务调度(context.scheduler)是 Lua 所新增、而纯 EliteScript 难以轻松做到的东西。先从把一个带分支或条件的能力转换为 Lua 开始,同时保持其他一切不变。 -
使用
context.script进行目标选取和区域几何。 脚本工具接受与 EliteScript 相同的字段名(targetType、shape、Target、Target2、range、offset、coverage),因此你可以继续把现有的 EliteScript 文档作为这些规范的参考。这让你在为逻辑层获得 Lua 灵活性的同时,仍能利用熟悉的模式。
新手进阶路径
如果你想从零开始学习这套系统,以下进阶顺序效果很好:
- 编写一个只包含
api_version = 1和on_spawn的文件。 - 让 Boss 发送一条消息或播放一个声音。
- 用
context.cooldowns添加一个冷却。 - 添加一个由玩家触发的钩子,例如
on_boss_damaged_by_player。 - 用
context.scheduler:run_after(...)添加一个延迟动作。 - 添加一个简单的原生 Lua 区域查询或一个简单的
context.script:target(...)。 - 只有到了那时,才开始转入轮转攻击、状态机和多步机制。
每一步都建立在前一步之上,你可以在每个阶段进行测试。不要试图把多阶段 Boss 作为你的第一个 Lua 能力来编写。
