Lua 脚本:故障排除
本页介绍编写或调试 FreeMinecraftModels 道具脚本时可能遇到的常见问题,以及使用延迟配置生成系统的技巧。如果您正在寻找可运行的示例,请参阅示例与模式。如果您刚刚入门,请参阅入门指南。
FMM 使用 MagmaCore 的 ScriptInstance 运行时。有关沙箱、调度器、区域、世界 API、实体表和玩家 UI,请参阅 MagmaCore Lua 脚本引擎。EliteMobs 的 Boss 技能和 NPC 脚本复用 Lua 沙箱,但提供各自的上下文表。
常见问题
1. 配置文件未加载 / 脚本未关联
症状: 道具已生成,但不响应点击或任何钩子。
原因和解决方法:
-
尚不存在
.yml配置文件。 FMM 会在首次生成道具时尝试异步创建默认配置(已启用、无脚本),且不会覆盖已有文件。添加脚本名后,成功完成/fmm reload,再放置新道具。修改 YAML 和 Lua 都需要重新加载。 -
配置中设置了
isEnabled: false。 打开模型文件旁边的.yml文件,将其设置为isEnabled: true。 -
scripts:列表为空。 添加您的脚本文件名:isEnabled: true
scripts:
- my_script.lua -
.yml文件名与模型文件名不匹配。 配置必须与模型文件具有相同的基本名称。例如,torch_01.fmmodel需要同一目录下的torch_01.yml。
2. 未找到脚本文件
症状: 控制台显示:[FMM Scripts] Script 'my_script.lua' not found in scripts/ folder
原因和解决方法:
-
目录错误。 脚本文件必须位于
plugins/FreeMinecraftModels/scripts/,而不是模型文件旁边。 -
文件名不匹配。
.yml中的名称必须与scripts/中的文件名大小写一致。建议填写.lua扩展名,但省略时 FMM 会自动补上。 -
文件不存在。 请仔细检查
.lua文件是否确实存在于scripts/文件夹中。 -
缓存定义或重名文件。 FMM 在提供者初始化时递归查找脚本,但按文件名关联,不按相对路径关联。各子文件夹中的名称必须唯一。添加或修改 Lua 文件后,需要成功执行
/fmm reload;只重新生成道具不会重新读取缓存的 Lua 代码。
3. 脚本完全无法加载
症状: 没有控制台错误,但没有钩子触发。
检查服务器控制台在启动时是否有 Lua 语法错误。最常见的原因是:
- 缺少
end来关闭函数或if块 - 括号不匹配
- 返回的表中钩子定义之间缺少逗号
- 文件不以
.lua结尾
发现文件时的失败会显示为无效或已停用脚本的警告,或加载错误。钩子和回调错误使用 [Lua] 块,包含文件名及执行上下文;只有能从异常提取行号时才会显示行号。
4. 钩子从未触发
症状: 您添加的启动日志已出现,但某个钩子始终不触发。
验证钩子名称是否与钩子参考完全一致。常见错误:
| 错误名称 | 正确名称 |
|---|---|
on_click | on_right_click 或 on_left_click |
on_interact | on_right_click |
on_hit | on_left_click |
on_tick | on_game_tick |
on_remove | on_destroy(仅在道具被永久移除时运行,区块卸载或重载时不会运行) |
on_arrow_hit | on_projectile_hit |
确认模型已加载、有可用的碰撞箱,且保护监听器未在道具回调前取消交互。已生成的道具使用盔甲架;更改配置选项不能让无效或未加载的模型变得可点击。
5. 超时 / 超出执行预算
症状: 控制台显示由 Lua VM 内部抛出的错误。调用超出预算时,会在执行过程中被中止,并显示以下消息之一:
Lua instruction budget exceeded (250000 instruction limit)
Lua CPU-time budget exceeded (50ms current-thread CPU limit)
Lua elapsed-time fallback budget exceeded (250ms fallback; current-thread CPU time unavailable)
每个钩子、回调或文件求值最多执行 250,000 条 Lua 指令,或消耗 50 ms 当前线程 CPU 时间,以先达到的限制为准。嵌套调用共享同一预算。如果 JVM 无法提供每线程 CPU 计时,则改用 250 ms 实际经过时间的限制;只有此时才会出现第三条消息。这意味着脚本在一次调用中执行了过多工作。常见原因包括:
- 在
on_game_tick中遍历过多实体 - 每 tick 创建过多粒子
- 执行高开销循环,没有将工作分散到多个 tick
修复: 用 context.cooldowns 限制高开销工作,或使用间隔合理的 scheduler:run_repeating() 代替 on_game_tick。
指令限制约束 Lua 循环。时间检查每 1024 条 Lua 指令及返回时执行,不会中途打断阻塞的 Java/Bukkit 方法。也应限制每个回调内部的工作量。
6. 点击钩子中 context.event 为 nil
这在 on_left_click 和 on_right_click 中通常不应该发生,但始终使用以下方式进行保护:
if context.event then
context.event.cancel()
end
在调度器回调内部,context.event 始终为 nil——这是预期行为。事件修改只能在事件钩子本身内部进行。
7. 动画不播放
症状: play_animation() 返回 false,或者没有可见的变化。
原因和解决方法:
-
动画名称错误。 名称必须与模型文件中定义的完全匹配。检查您的
.bbmodel或.fmmodel文件以获取正确的动画名称。 -
模型没有动画。 并非所有模型都有动画。验证模型文件是否确实包含动画数据。
-
玩家缺少资源包或处于模型显示范围之外。 玩家需要当前资源包及可见的模型。
-
请求排队或被抑制。
blend传入true使用单个队列位置,不是交叉淡入淡出。后续请求可能替换它,自定义动画期间的攻击请求也可能返回true而不打断动画。使用动画中列出的位置参数,区分请求被接受与实际画面已显示。
8. 粒子不出现
- 粒子名称接受
FLAME和flame;API 会统一大小写后查找 Bukkit 粒子。 - 验证位置位于已加载的区块中。如果附近没有玩家,区块可能已卸载。
- 验证
count至少为 1。 - 某些粒子(如
DUST)需要特定的额外数据,基本的spawn_particle()可能不支持。使用标准粒子如FLAME、HEART、HAPPY_VILLAGER、NOTE、ENCHANT等。
9. 声音不播放
- 声音接受不区分大小写的 Bukkit 枚举名称,也接受
minecraft:block.note_block.harp等有效键和自定义资源包的声音键。客户端资源包中必须包含该声音。 - 验证位置坐标是否正确(不全为零或 NaN)。
- 验证音量大于 0。
10. 状态意外重置
context.state 属于一个脚本实例,并在该实例活动期间保留。同一道具的 scripts: 列表中不同条目不共享状态。如果似乎重置了:
- 道具可能已被移除并重新生成,或者其所在区块被卸载后又重新加载。每次生成或区块加载都会创建新实例。必须在这种情况下保留的值,请用
prop:set_persistent_data保存。 - 成功的重新加载或脚本错误可能关闭旧实例。查看控制台;仅修改文件不会复活已关闭的实例,仍需重新加载并关联。
- 您可能在调度器回调中使用了错误的上下文变量来读取状态。始终使用回调自身的上下文参数。
11. os 库不可用
症状: 脚本崩溃并出现类似 attempt to index nil (os) 的错误。
os 库已从 Lua 沙箱中完全移除。您无法使用 os.time()、os.clock() 或任何其他 os 函数。
解决方法: 使用 context.world:get_time() 获取世界时间,或者使用状态标志和 on_game_tick 的刻计数器手动跟踪经过的时间。对于冷却时间,优先使用 context.cooldowns:check_local(key, ticks)。
12. 物品脚本未激活
装备物品的脚本格式已停用。包含非空 scripts: 列表或旧版无命名空间附魔的自定义物品会被跳过并记录日志。请改用当前的自定义物品定义及带命名空间的附魔。道具脚本仍使用 scripts:。
13. 物品定义不可用
查看启动或重新加载警告中的准确文件名。不支持的材质,例如旧服务器上的新版 Minecraft 物品,也会使定义被跳过。请按服务器版本修正或替换定义;FMM 不会重写它或凭空选择替代材质。
14. 钩子触发但脚本在实体方法上崩溃
症状: on_right_click 等钩子可以工作,但调度回调中调用 entity:damage() 或 entity:push() 时出现 attempt to call nil 等错误。
原因: context.world:get_nearby_entities() 返回范围内所有实体,包括掉落物、经验球和区域效果云等非生物实体。非生物实体没有 damage()、push() 或 add_potion_effect() 等生物方法。
解决方法: 在调用生物实体方法之前,始终用 if entity.damage then 进行保护:
local entities = context.world:get_nearby_entities(x, y, z, radius)
for _, entity in ipairs(entities) do
if entity.damage then
-- Safe to call living entity methods
entity:damage(5.0)
entity:push(0, 0.5, 0)
end
end
15. 调度回调中缺少仅事件可用的数据
症状: 在 scheduler:run_later() 或 scheduler:run_repeating() 回调内部,来自点击/战斗事件的数据缺失。
原因: 调度回调收到新上下文,不保留原始 Bukkit 事件或通用区域事件的玩家,context.event 始终为 nil。道具脚本仅在点击、区域进入和离开等玩家触发的钩子中收到顶层 context.player。之后需要该玩家时,应在原始钩子内从 context.player 或 context.event.player 保存引用。
解决方法: 访问世界和状态时使用回调的新上下文。需要原始事件中的玩家或实体时,在调度前保存它,并在执行时确认其仍有效。
on_right_click = function(context)
local clicked_player = context.player or (context.event and context.event.player)
context.scheduler:run_later(20, function(later_context)
-- Prop scripts should use a captured player when they need the clicker.
local player = later_context.player or clicked_player
if player and player.is_valid then
player:send_message("&aDelayed message!")
end
end)
end
延迟配置生成的工作原理
了解延迟配置系统有助于避免设置新道具时的困惑:
-
首次生成: 当道具生成且不存在相邻的
.yml文件时,FMM 异步创建配置。这意味着文件在后台线程中写入,不会立即可用。 -
默认值: 生成的配置包含
isEnabled: true和空的scripts: []列表。 -
首次生成时没有脚本: 由于配置是在道具已经生成之后创建的(且没有列出脚本),道具在首次生成时不会关联任何脚本。
-
编辑、重新加载、重新生成: 添加脚本名后,成功完成
/fmm reload。道具设置、放置检查和菜单脚本列表共用已准备好的目录。新道具使用该目录,不会重读编辑后的 YAML。修改 Lua 也需要重新加载。 -
配置位置:
.yml文件创建在与模型文件相同的目录中,具有相同的基本名称。例如:- 模型:
plugins/FreeMinecraftModels/models/fountain.fmmodel - 配置:
plugins/FreeMinecraftModels/models/fountain.yml
- 模型:
- 将模型放入
models/ - 将脚本放入
scripts/ - 生成道具一次(生成
.yml) - 编辑
.yml添加scripts: [my_script.lua] - 重新加载以发现 Lua 文件,再放置新道具并确认您添加的启动日志
阅读错误消息
钩子和回调错误会生成 [Lua] 块,标明文件和执行上下文。异常包含可识别的代码位置时才会显示行号。文件发现和配置错误可能使用不同的警告。
典型的错误如下所示:
[Lua] Error in 'my_door.lua' at line 12 during 'on_right_click':
[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 | 函数接收了错误类型的参数。消息显示了期望的类型与实际类型。 |
| 执行预算超限 | Lua instruction budget exceeded (250000 instruction limit) 或 Lua CPU-time budget exceeded (50ms current-thread CPU limit):调用被中止,脚本实例被禁用。 |
在深入代码之前,请始终阅读完整的 [Lua] 错误消息。它通常会直接指向解决方法。
不要假设存在未文档化的方法
FMM Lua API 公开了一组特定的方法。不要假设存在简写名称或替代名称。常见错误:
context.prop:get_location()-- 不存在。使用context.prop.current_location(字段,不是方法)。context.prop:set_animation("open")-- 不存在。使用context.prop:play_animation("open", true, true)。context.event:setCancelled(true)-- 不存在。使用context.event.cancel()。context.cooldowns:check_global(...)-- 不存在。全局冷却使用context.cooldowns:global_ready()和context.cooldowns:set_global(ticks)。context.player-- 仅在涉及玩家的 FMM 道具钩子内设置,例如点击、监控区域进出,以及由玩家射出投射物时的on_projectile_hit。它不是通用的道具拥有者字段,在生命周期钩子和调度回调中为nil。context.boss-- 不存在。那是 EliteMobs 的。在 FMM 中使用context.prop。
请在道具 API及其链接的共用参考中确认支持的方法。看似合理的名称不能证明方法存在。
调试技巧
1. 大量使用 context.log:info()
调试时在每个步骤添加日志消息:
on_right_click = function(context)
context.log:info("Right click received!")
context.log:info("Event present: " .. tostring(context.event ~= nil))
context.log:info("State is_open: " .. tostring(context.state.is_open))
end
2. 在启动时检查控制台
在 on_spawn 中添加 context.log:info("Prop script started"),重新加载后放置新道具。在控制台查找这条日志及脚本或配置错误。FMM 不会为每次关联输出成功消息。
3. 验证模型文件路径
.yml 配置必须是模型文件的同级文件(相同目录、相同基本名称)。通过检查以下内容来验证:
- 模型文件路径:
plugins/FreeMinecraftModels/models/my_model.fmmodel - 配置文件路径:
plugins/FreeMinecraftModels/models/my_model.yml
4. 单独测试钩子
构建复杂脚本时,先单独测试每个钩子。一次添加一个钩子,验证其触发后再添加下一个。
5. 检查钩子名称的拼写错误
钩子名拼写错误是常见原因。未知钩子会使整个脚本在验证时被拒绝;请查看控制台错误,不要期待该文件的其他钩子仍然运行。
初学者学习路径
如果您想从零开始学习此系统,以下学习路径效果很好:
- 编写一个只包含
api_version = 1和记录消息的on_spawn的文件。 - 将脚本添加到道具配置中,验证日志消息是否出现。
- 添加
on_right_click并在道具被点击时记录日志。 - 添加带有
context.event.cancel()的on_left_click,取消默认点击伤害。 - 在右键点击时播放动画。
- 在右键点击时添加声音。
- 添加状态和切换行为。
- 添加区域监视以实现接近检测。
- 此后才开始编写带有调度器的复杂多钩子脚本。
每个步骤都建立在前一步的基础上,您可以在每个阶段进行测试。