跳到主要内容

Lua 脚本:故障排除

本页介绍编写或调试 FreeMinecraftModels 道具脚本时可能遇到的常见问题,以及使用延迟配置生成系统的技巧。如果您正在寻找可运行的示例,请参阅示例与模式。如果您刚刚入门,请参阅入门指南。

MagmaCore Lua 运行时

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_clickon_right_click 或 on_left_click
on_interacton_right_click
on_hiton_left_click
on_tickon_game_tick
on_removeon_destroy(仅在道具被永久移除时运行,区块卸载或重载时不会运行)
on_arrow_hiton_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

延迟配置生成的工作原理​

了解延迟配置系统有助于避免设置新道具时的困惑:

  1. 首次生成: 当道具生成且不存在相邻的 .yml 文件时,FMM 异步创建配置。这意味着文件在后台线程中写入,不会立即可用。

  2. 默认值: 生成的配置包含 isEnabled: true 和空的 scripts: [] 列表。

  3. 首次生成时没有脚本: 由于配置是在道具已经生成之后创建的(且没有列出脚本),道具在首次生成时不会关联任何脚本。

  4. 编辑、重新加载、重新生成: 添加脚本名后,成功完成 /fmm reload。道具设置、放置检查和菜单脚本列表共用已准备好的目录。新道具使用该目录,不会重读编辑后的 YAML。修改 Lua 也需要重新加载。

  5. 配置位置: .yml 文件创建在与模型文件相同的目录中,具有相同的基本名称。例如:

    • 模型:plugins/FreeMinecraftModels/models/fountain.fmmodel
    • 配置:plugins/FreeMinecraftModels/models/fountain.yml
快速设置流程
  1. 将模型放入 models/
  2. 将脚本放入 scripts/
  3. 生成道具一次(生成 .yml)
  4. 编辑 .yml 添加 scripts: [my_script.lua]
  5. 重新加载以发现 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. 检查钩子名称的拼写错误​

钩子名拼写错误是常见原因。未知钩子会使整个脚本在验证时被拒绝;请查看控制台错误,不要期待该文件的其他钩子仍然运行。


初学者学习路径​

如果您想从零开始学习此系统,以下学习路径效果很好:

  1. 编写一个只包含 api_version = 1 和记录消息的 on_spawn 的文件。
  2. 将脚本添加到道具配置中,验证日志消息是否出现。
  3. 添加 on_right_click 并在道具被点击时记录日志。
  4. 添加带有 context.event.cancel() 的 on_left_click,取消默认点击伤害。
  5. 在右键点击时播放动画。
  6. 在右键点击时添加声音。
  7. 添加状态和切换行为。
  8. 添加区域监视以实现接近检测。
  9. 此后才开始编写带有调度器的复杂多钩子脚本。

每个步骤都建立在前一步的基础上,您可以在每个阶段进行测试。


下一步​