Lua 脚本:入门指南
本页将教您编写第一个 FreeMinecraftModels 道具的 Lua 脚本,从空文件一直到一个可工作的交互式道具。读完之后,您将理解钩子、context、道具 API 以及每个道具脚本文件的通用结构。
熟悉基础知识后,请继续阅读配套页面:
- 道具 API --
context.prop、context.event、context.world和其他 context API - 示例与模式 -- 可供学习和改编的完整可运行脚本
- 故障排除 -- 常见错误、调试技巧和 QC 检查清单
Lua 道具脚本目前为实验性功能。随着 FreeMinecraftModels 的演进,钩子名称、辅助方法和行为仍可能发生变化,因此在生产服务器上使用前请仔细测试。
FreeMinecraftModels 使用 MagmaCore Lua 运行时。如果您已经在为 EliteMobs 编写 Lua 技能,那么核心概念——返回一个表的脚本文件、api_version、钩子、context、状态、冷却、调度以及沙盒——都会让您感到熟悉。但具体的钩子和上下文方法名称仍取决于插件:
- EliteMobs 的脚本运行在 Boss 上,拥有
on_boss_damaged_by_player、on_enter_combat等钩子。 - FMM 道具脚本运行在道具上,拥有
on_right_click、on_left_click、on_zone_enter等钩子。 - FMM 物品脚本运行在自定义物品上,拥有
on_equip、on_attack_entity、on_consume、on_game_tick等钩子。
本页记录的 context.world、context.zones、context.scheduler、context.state 和 context.log API 是 FreeMinecraftModels/MagmaCore 版本。EliteMobs 的 NPC 脚本使用相同的通用 MagmaCore 表并额外提供 context.npc;EliteMobs 的 Boss 技能则对若干表使用 Boss 专用的变体。本页涵盖的是 FMM 道具与物品特有的内容。
什么是道具脚本
道具脚本是位于 plugins/FreeMinecraftModels/scripts/ 文件夹中的独立 .lua 文件。它们通过模型文件旁边的 YAML 配置文件引用,并在道具被生成到世界中时运行。
道具脚本擅长什么
道具脚本在以下场景中表现出色:
- 响应玩家点击的交互式道具(门、拉杆、按钮)
- 玩家无法破坏的无敌装饰道具
- 检测玩家进入或离开区域的接近触发器
- 在交互或定时器触发时播放动画的道具
- 被点击或靠近时播放声音的道具
- 任何需要超越静态装饰逻辑的道具行为
如果您的道具纯粹是装饰性的且不需要交互,则不需要脚本。
什么是物品脚本
物品脚本使用与道具脚本相同的 .lua 文件格式和相同的 scripts/ 文件夹。区别在于它们绑定到自定义物品——即在 YML 配置文件中设置了 material: 字段的模型。道具脚本在道具实体被生成到世界中时运行,而物品脚本则在玩家装备该自定义物品(主手、副手或护甲栏位)时运行,并在物品被卸下时停止。
物品脚本的工作方式
- 激活: 当玩家装备一件自定义 FMM 物品时,会创建一个脚本实例。脚本按玩家、按物品类型划分——每个(玩家, itemId)组合对应一个
ScriptInstance。 - 停用: 当物品被卸下(移出激活栏位、被丢弃,或玩家断开连接)时,脚本实例被销毁。
- 物品识别: 自定义物品通过
fmm_item_idPDC(PersistentDataContainer)键来识别,它与道具的model_id不同。要获得带有正确标签的物品,请使用/fmm giveitem <id>或管理员菜单。 - 上下文: 物品钩子收到的
context包含context.player、context.item、context.world、context.state、context.scheduler、context.log,以及(在适用时)context.event。
物品脚本擅长什么
物品脚本适用于以下需求:
- 带有特殊能力的自定义武器(冰霜之剑、魔杖)
- 拥有独特右键或潜行点击动作的工具
- 带有自定义效果的消耗品
- 穿戴时提供被动效果的护甲
- 记录使用次数或具有有限耐久度的物品
- 任何超出原版机制的手持物品行为
本页的目标读者
本页面面向三类读者:
- 已经了解 EliteMobs Lua 脚本,想学习 FMM 特有钩子和 API 的人
- Lua 脚本新手,需要完整且名称精确的道具参考的人
- 使用 AI 起草道具脚本,需要足够细节来辨别 AI 是否编造了虚假内容的人
您不需要在编写有用的道具脚本之前成为一名完整的 Lua 开发者。对于大多数实用的道具脚本,您真正需要的只是:
- 如何在返回的表中放置有效的钩子
- 如何从
context读取值 - 如何用
if ... then return end提前退出 - 如何精确调用几个辅助方法
Lua 快速入门
编写 FMM 脚本并不需要您成为 Lua 专家。大多数脚本只会用到少数几个概念:变量(local x = 5)、函数(function foo() end)、if 检查(if x then ... end)、表({key = value})以及 nil(Lua 表示“什么都没有”的值)。语法很轻量——没有分号,没有花括号,只用 end 结束代码块。
完整的逐步讲解和示例请参阅 MagmaCore Lua 脚本引擎 —— Lua 快速入门。该入门内容在所有 Nightbreak 插件之间共享,因此学一次就能处处适用。
文件存放位置
脚本文件
将 .lua 文件放在中央 scripts 文件夹中:
plugins/
FreeMinecraftModels/
scripts/
invulnerable.lua
interactive_door.lua
proximity_sound.lua
FMM 在启动时会发现 plugins/FreeMinecraftModels/scripts/ 中的所有 .lua 文件。
模型文件和配置文件
每个模型文件可以在同一目录中有一个对应的 .yml 配置文件:
plugins/
FreeMinecraftModels/
models/
torch_01.fmmodel
torch_01.yml <-- torch_01 的脚本配置
scripts/
invulnerable.lua <-- 被 torch_01.yml 引用
.yml 配置是连接模型与其脚本的桥梁。
配置文件格式
模型文件旁边的 YAML 配置文件有两个字段:
isEnabled: true
voxelize: false
solidify: false
scripts:
- invulnerable.lua
对于自定义物品(玩家可以手持或装备的模型),您还需要设置 material 字段,并可选地设置 name、lore 和 enchantments:
isEnabled: true
material: DIAMOND_SWORD
name: "&bFrost Blade"
lore:
- "&7A sword forged in eternal ice"
- "&7Slows enemies on hit"
enchantments:
- "SHARPNESS,5"
- "UNBREAKING,3"
scripts:
- frost_sword.lua
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
isEnabled | boolean | true | 此道具/物品的脚本是否激活 |
scripts | 字符串列表 | [] | scripts/ 文件夹中 .lua 脚本的文件名 |
voxelize | boolean | false | 将放置对齐到 90 度旋转和方块网格 |
solidify | boolean | false | 在道具占用范围内放置仅数据包的屏障方块(需要 voxelize) |
material | string | "" | 一个有效的 Bukkit Material 名称(例如 DIAMOND_SWORD)。设置该字段会把模型变成玩家可以手持或装备的自定义物品,从而启用物品脚本系统 |
name | string | "" | 自定义物品的显示名称。支持 & 颜色代码 |
lore | 字符串列表 | [] | 显示在物品提示框中的描述行。支持 & 颜色代码 |
enchantments | 字符串列表 | [] | 应用到物品上的附魔。格式:"ENCHANTMENT_NAME,LEVEL"(例如 "SHARPNESS,5") |
您可以将多个脚本附加到同一个道具。每个脚本都是独立的实例。
- 物品 ID 由去掉扩展名的 YML 文件名得出。例如
frost_sword.yml得到的物品 ID 是frost_sword。这就是/fmm giveitem和fmm_item_idPDC 键所使用的 ID。 - 物品只会绑定
scripts:列表中的一个脚本。FMM 按顺序检查条目,使用第一个能解析成功的脚本,随后忽略其余条目。与物品不同,道具会把所有能解析成功的脚本都作为独立实例运行。 - 如果省略
.lua扩展名,脚本文件名会自动补全,因此在scripts:列表中frost_sword和frost_sword.lua是等价的。
配置延迟生成
当道具生成且没有对应的 .yml 文件时,FMM 会自动创建一个默认配置文件,包含 isEnabled: true 和空的 scripts: 列表。这是异步进行的,因此道具在首次生成时不会有脚本——只有在配置创建后并编辑添加脚本文件名之后才会生效。
这意味着:
- 将模型文件放入
models/ - 生成道具一次(FMM 自动创建
.yml) - 编辑生成的
.yml添加您的脚本文件名 - 重新生成道具或重载服务器(脚本现在已激活)
钩子参考
每个 Lua 道具脚本文件返回一个表。该表中的每个键(除了 api_version 和 priority)必须是下面列出的钩子之一。运行时在对应的游戏事件触发时调用匹配的函数。
| 钩子 | 触发时机 | 说明 |
|---|---|---|
on_spawn | 道具被生成到世界中 | 脚本绑定时运行一次 |
on_game_tick | 每个服务器刻一次(50 毫秒) | 仅在脚本定义了此钩子时激活 |
on_destroy | 道具从世界中移除 | 清理钩子 |
on_left_click | 玩家左键点击(攻击)道具 | context.event 是伤害事件 |
on_right_click | 玩家右键点击道具 | context.event 是交互事件 |
on_zone_enter | 玩家进入监视区域 | 需要先设置区域监视 |
on_zone_leave | 玩家离开监视区域 | 需要先设置区域监视 |
当前的脚本校验器接受道具脚本中的 on_projectile_hit,但当前运行时尚未把投射物命中事件分发给道具脚本。如果需要与脚本化物品绑定的投射物行为,请使用物品的 on_projectile_hit;如果需要插件侧处理带模型实体的投射物,请使用 Bukkit 的 ModeledEntityHitByProjectileEvent API。
物品钩子参考
物品脚本和道具脚本一样返回一个表,包含 api_version = 1 和钩子函数。以下钩子可用于物品脚本。所有物品钩子都会接收 context,其中包含 context.player、context.item 以及(在适用时)context.event。
说明列中标注了底层的 Bukkit 事件族。Lua 封装不会暴露原始的 Bukkit 特有字段,例如 target、block、projectile 或 item;当您需要额外的上下文时,请使用 context.player、context.event.player 以及实体/世界辅助查询。
战斗钩子
| 钩子 | 触发时机 | 说明 |
|---|---|---|
on_attack_entity | 玩家手持道具攻击一个实体 | context.event 是伤害事件 |
on_kill_entity | 玩家手持道具击杀一个实体 | context.event 是死亡事件 |
on_take_damage | 玩家在装备道具时受到伤害 | context.event 是伤害事件 |
on_shield_block | 玩家用盾牌格挡伤害 | context.event 是伤害事件 |
on_shoot_bow | 玩家射出弓箭 | context.event 是弓箭射击事件 |
on_projectile_hit | 玩家射出的投射物击中某物 | context.event 是投射物命中事件 |
on_projectile_launch | 玩家发射一个投射物 | context.event 是投射物发射事件 |
交互钩子
| 钩子 | 触发时机 | 说明 |
|---|---|---|
on_right_click | 玩家手持道具右键点击 | context.event 是交互事件 |
on_left_click | 玩家手持道具左键点击 | context.event 是交互事件 |
on_shift_right_click | 玩家手持道具 Shift+右键点击 | context.event 是交互事件 |
on_shift_left_click | 玩家手持道具 Shift+左键点击 | context.event 是交互事件 |
on_interact_entity | 玩家手持道具右键点击一个实体 | context.event 是实体交互事件 |
装备钩子
| 钩子 | 触发时机 | 说明 |
|---|---|---|
on_equip | 道具被装备(移入激活槽位) | 适合初始化状态的地方 |
on_unequip | 道具被卸下(移出激活槽位) | 适合清理的地方 |
on_swap_hands | 玩家在主手和副手之间切换道具 | context.event 是切换事件 |
on_drop | 玩家丢弃道具 | context.event 是丢弃事件 |
实用钩子
| 钩子 | 触发时机 | 说明 |
|---|---|---|
on_break_block | 玩家手持道具破坏一个方块 | context.event 是方块破坏事件 |
on_consume | 玩家消耗道具(食物/药水) | context.event 是消耗事件 |
on_item_damage | 道具受到耐久度损耗 | context.event 是道具损耗事件 |
on_fish | 玩家使用钓鱼竿 | context.event 是钓鱼事件 |
on_death | 玩家在装备道具时死亡 | context.event 是死亡事件 |
生命周期钩子
| 钩子 | 触发时机 | 说明 |
|---|---|---|
on_game_tick | 道具装备期间的每个服务器刻 | 谨慎使用——每秒运行 20 次 |
最小文件契约
每个 Lua 道具脚本必须 return 一个表。
必填和可选的顶层字段
| 字段 | 必填 | 类型 | 说明 |
|---|---|---|---|
api_version | 是 | Number | 目前必须为 1 |
priority | 否 | Number | 执行优先级。较小的值先执行。默认为 0 |
| 支持的钩子键 | 否 | Function | 必须使用钩子参考中列出的精确钩子名称之一 |
验证规则
- 文件必须返回一个表。
api_version为必填,目前必须为1。priority如果存在必须为数字。- 每个额外的顶层键必须是受支持的钩子名称。
- 每个钩子键必须指向一个函数。
- 未知的顶层键会被拒绝。
priority 有助于让脚本在各个基于 MagmaCore 的运行时之间保持可移植性,但 FreeMinecraftModels 当前运行时的执行顺序是由配置决定的。请在模型的 scripts: 列表中按您希望的执行顺序排列道具脚本。
辅助函数和局部常量应放在最后的 return 上方,而不是在返回的表内部。
一步步构建您的第一个可工作的道具脚本
步骤 1 之前:设置配置
- 将模型文件(例如
my_prop.fmmodel)放入plugins/FreeMinecraftModels/models/ - 生成道具一次以生成
.yml配置 - 在
plugins/FreeMinecraftModels/scripts/first_test.lua创建您的脚本文件 - 编辑
plugins/FreeMinecraftModels/models/my_prop.yml:
isEnabled: true
scripts:
- first_test.lua
- 重新生成道具或重载服务器
步骤 1:让文件加载
return {
api_version = 1,
on_spawn = function(context)
end
}
如果这在控制台中无错误地加载,您证明了:
- 文件是有效的 Lua
- FMM 在
scripts/文件夹中找到了它 - 配置正确引用了它
- 返回表的结构正确
步骤 2:让道具做一件可见的事
return {
api_version = 1,
on_spawn = function(context)
context.log:info("Prop script loaded for: " .. (context.prop.model_id or "unknown"))
end
}
检查服务器控制台。如果您看到日志消息,说明您的钩子正在触发。
步骤 3:响应玩家点击
return {
api_version = 1,
on_right_click = function(context)
context.log:info("Prop was right-clicked!")
end
}
在游戏中右键点击道具。如果控制台显示消息,说明点击钩子正在工作。
步骤 4:取消伤害使道具无敌
return {
api_version = 1,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}
这是预制 invulnerable.lua 脚本使用的模式。它取消伤害事件,使道具底层的盔甲架无法被摧毁。
步骤 5:点击时播放动画
return {
api_version = 1,
on_right_click = function(context)
context.prop:play_animation("open", true, false)
end
}
这会在道具模型上播放 "open" 动画,带有混合且不循环。
什么是 context?
每个钩子函数接收一个名为 context 的参数。把它想象成一个工具箱,FMM 在每次发生事件时交给您——它包含了您与道具、世界、区域等交互所需的一切。
您不需要自己创建 context——FMM 会创建它并传递给您的钩子。关于共享上下文 API(context.state、context.log、context.cooldowns、context.scheduler、context.world、context.zones)的完整细节,请参阅 MagmaCore Lua 脚本引擎 页面。
关键 context API
以下是可用功能的摘要。完整详情请参见道具 API。
-
context.prop—— (仅道具脚本)道具实体。提供model_id、current_location、play_animation()和stop_animation()。 -
context.item—— (仅物品脚本)自定义物品。提供id、material()、get_amount()、set_amount()、consume()、get_uses()、set_uses()、get_name()、set_name()、get_lore()、set_lore()、get_durability()、get_durability_percentage()、use_durability()和use_durability_percentage()。完整细节请参见道具与物品 API。 -
context.player—— 由玩家驱动的钩子中的玩家。物品脚本从物品持有者解析该值;道具点击钩子和通用区域钩子则从触发的玩家解析。在道具生命周期钩子、道具计划回调以及不涉及玩家的钩子中,它为nil。 -
context.event—— 触发此钩子的 Bukkit 事件或玩家行为者的轻量封装。在点击、战斗、交互和通用区域钩子中可用。提供event.player、is_cancelled,以及在底层 Bukkit 事件可取消时提供cancel()/uncancel();它不会暴露target、block、projectile或item等 Bukkit 特有字段。在没有事件也没有玩家行为者的钩子中(如on_spawn、on_game_tick和on_equip)为nil。 -
context.state—— 在脚本实例生命周期内持续存在的普通 Lua 表。参见 context.state。 -
context.cooldowns—— 本地和全局冷却辅助工具。常规的按脚本冷却请使用context.cooldowns:check_local("key", ticks)。参见 context.cooldowns。 -
context.log—— 控制台日志记录。参见 context.log。 -
context.scheduler—— 延迟和重复任务。参见 context.scheduler。 -
context.world—— 世界交互:粒子、声音、方块查询、闪电、附近实体。参见 context.world。 -
context.zones—— 创建和监视空间区域(球体、圆柱体、长方体)。参见 context.zones。
方法语法:: vs .
关于 Lua 中 : 与 . 方法语法的说明,请参阅 MagmaCore Lua 脚本引擎 页面。FMM API 接受两种形式。
即拷即用的模板
最小的有效道具脚本
return {
api_version = 1,
on_spawn = function(context)
end
}
无敌道具模板
return {
api_version = 1,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}
交互式道具模板
return {
api_version = 1,
on_spawn = function(context)
context.state.is_active = false
end,
on_right_click = function(context)
context.state.is_active = not context.state.is_active
if context.state.is_active then
context.prop:play_animation("activate", true, true)
else
context.prop:stop_animation()
end
end
}
最小的有效物品脚本
return {
api_version = 1,
on_equip = function(context)
end
}
带右键动作的物品模板
return {
api_version = 1,
on_right_click = function(context)
if not context.cooldowns:check_local("activate", 40) then return end
-- Your action here
context.player:send_message("&aItem activated!")
end
}
较大的文件布局
local ANIMATION_NAME = "idle"
local function do_something(context)
context.log:info("Doing something!")
end
return {
api_version = 1,
priority = 0,
on_spawn = function(context)
context.state.task_id = nil
end,
on_right_click = function(context)
do_something(context)
end,
on_destroy = function(context)
if context.state.task_id ~= nil then
context.scheduler:cancel(context.state.task_id)
end
end
}
第一个实际工作流程
构建全新的道具脚本时,请按以下顺序进行:
- 创建
.lua文件并让on_spawn正常工作。 - 将脚本文件名添加到道具的
.yml配置中。 - 切换到您实际需要的钩子(例如
on_right_click)。 - 先添加一条日志消息,然后再添加动画或特效。
- 添加一个真实的特效(动画、声音、粒子)。
- 只有在此之后,才添加辅助函数、state、scheduler 逻辑或区域。
这个顺序能极大简化调试,因为每次只改变一件事。
预制脚本
FMM 附带四个预制的 Lua 脚本:
invulnerable.lua—— 取消左键点击伤害事件,使道具不可摧毁。这是最简单实用的道具脚本。pickupable.lua—— 允许玩家通过敲击道具三次来拾取它。每次敲击都会在道具上播放受伤动画,第三次敲击时道具会被移除并掉落放置物品供玩家拾取。storage_double.lua—— 把道具变成一个大箱子(54 个格子)。右键点击会打开一个持久化的物品栏 GUI。会播放开关动画和音效。内容会保存到道具上并在服务器重启后保留。道具被摧毁时,所有内容都会掉落。storage_single.lua—— 与storage_double相同,但只有 3 行(27 个格子)而不是 6 行。
更多示例请参见示例与模式页面。
Lua 沙盒
道具脚本和物品脚本运行在与 EliteMobs 相同的沙盒化 LuaJ 环境中。沙盒限制完全相同。已移除的全局变量和可用标准库函数的完整列表请参见 MagmaCore Lua 脚本引擎 页面。
下一步
- 道具 API --
context.prop、context.event、context.world、context.zones和context.scheduler的完整参考 - 示例与模式 -- 附带详解的完整可运行脚本
- 故障排除 -- 常见问题、调试技巧和 QC 检查清单
如果您同时在编写 EliteMobs 的 Lua 技能,共享 API(context.world、context.zones、context.scheduler、context.state、context.log)的工作方式完全相同。Boss 特有的 API 请参见 EliteMobs Lua 文档。
物品脚本定义 fmm_item_id、material、name、lore、enchantments(例如 "SHARPNESS,5")和 script,并可用 /fmm giveitem <id> 获取。首个匹配的已装备物品接收 context.item;钩子提供相应上下文时也可使用 context.cooldowns:check_local("key", ticks)、context.event.player 和 context.npc。