Lua 脚本:道具 API
本页介绍 FreeMinecraftModels 道具脚本中的 context.prop、context.event、context.player、context.projectile、context.world、context.zones、context.scheduler、context.state、context.cooldowns 和 context.log。初次使用请先阅读入门。
context.prop
提供道具信息和动画操作。FMM 按道具缓存此表;current_location 等字段在读取时查询,因此仍然反映当前状态。
字段
| 字段 | 类型 | 说明 |
|---|---|---|
prop.model_id | 字符串 | 蓝图模型名称,例如 "torch_01" |
prop.current_location | location 表 | 道具当前的位置,读取时查询,不是固定快照 |
位置表包含 x、y、z、world、yaw、pitch。
示例:读取道具信息
return {
api_version = 1,
on_spawn = function(context)
context.log:info("Prop spawned: " .. (context.prop.model_id or "unknown"))
local loc = context.prop.current_location
if loc then
context.log:info("Location: " .. loc.x .. ", " .. loc.y .. ", " .. loc.z)
end
end
}
prop:play_animation(name, blend, loop)
请求道具模型播放指定名称的动画。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
name | 字符串 | 必填 | 模型文件中的动画名 |
blend | 布尔 | true | true 在当前状态的 tick 后排队切换,false 立即中断。不是交叉淡化 |
loop | 布尔 | true | 是否循环自定义动画;内置状态使用各自的规则 |
接受请求时返回 true,无法接受时返回 false。排队或被抑制的请求也可能被接受。终止状态 death 拒绝后续请求。自定义动画期间的攻击请求会被接受,但不会中断该动画。队列只有一个位置,新排队请求会替换旧请求。接受请求不证明客户端已经显示动画,详见动画。
示例
return {
api_version = 1,
on_right_click = function(context)
local success = context.prop:play_animation("open", true, false)
if not success then
context.log:warn("Animation 'open' not found on this model!")
end
end
}
prop:stop_animation()
清空队列,有 idle 时回到该状态,否则停止共享动画引擎的当前状态。终止状态 death 仍会保留,不过 Bedrock 后端会收到停止请求。不接受参数。
示例
return {
api_version = 1,
on_right_click = function(context)
context.prop:stop_animation()
end
}
prop:hurt_visual()
显示红色受伤闪烁,不造成实际伤害。不接受参数。
示例
return {
api_version = 1,
on_left_click = function(context)
-- Flash red when punched, but don't actually take damage
if context.event then
context.event.cancel()
end
context.prop:hurt_visual()
end
}
prop:pickup()
从世界移除道具,并在原位置掉落放置用的纸质物品。用纸右键方块可以重新放置道具。
不接受参数,也没有返回值。移除和掉落操作排入 Bukkit 主线程。此方法不检查权限,也不把保存的物品栏或书本数据转移到纸上。调用前请检查访问权限并处理保存内容。回收已经排队时,不要重复调用 pickup()。
示例
return {
api_version = 1,
on_right_click = function(context)
-- Let players pick up the prop by right-clicking it
context.prop:pickup()
end
}
prop:mount(player)
让玩家坐到第一个空闲挂载点。模型必须有挂载点骨骼。
| 参数 | 类型 | 说明 |
|---|---|---|
player | 实体表 | 玩家,例如 context.player 或 context.event.player |
找到玩家、道具有挂载点且乘坐操作已排队时返回 true。基本引用无效时返回 false。true 不保证执行时仍有空位。
右键脚本自行控制座位时,请取消 context.event,避免 FMM 的默认乘坐处理也尝试让玩家入座。
示例
return {
api_version = 1,
on_right_click = function(context)
if context.event then context.event.cancel() end
local player = context.event and context.event.player
if player then
context.prop:mount(player)
end
end
}
prop:dismount(player)
让玩家离开道具上的座位。
| 参数 | 类型 | 说明 |
|---|---|---|
player | 实体表 | 玩家 |
找到玩家、道具有挂载管理器且离座检查已排队时返回 true;基本引用无效时返回 false。
示例
return {
api_version = 1,
on_right_click = function(context)
if context.event then context.event.cancel() end
local player = context.event and context.event.player
if player then
-- Toggle mount/dismount
local passengers = context.prop:get_passengers()
for i = 1, #passengers do
if passengers[i].uuid == player.uuid then
context.prop:dismount(player)
return
end
end
context.prop:mount(player)
end
end
}
prop:get_passengers()
返回当前所有乘客实体表组成的 Lua 数组。不接受参数。
示例
return {
api_version = 1,
on_game_tick = function(context)
local passengers = context.prop:get_passengers()
if #passengers > 0 then
context.log:info("Prop has " .. #passengers .. " passenger(s)")
end
end
}
prop:has_mount_points()
以 true 或 false 返回模型是否有挂载点骨骼。不接受参数。
示例
return {
api_version = 1,
on_right_click = function(context)
if context.event then context.event.cancel() end
local player = context.event and context.event.player
if player and context.prop:has_mount_points() then
context.prop:mount(player)
end
end
}
prop:spawn_elitemobs_boss(filename, x, y, z)
在指定位置生成 EliteMobs 自定义 Boss,需要安装 EliteMobs。
| 参数 | 类型 | 说明 |
|---|---|---|
filename | 字符串 | Boss 文件名,例如 "my_boss.yml" |
x | 数字 | X 坐标 |
y | 数字 | Y 坐标 |
z | 数字 | Z 坐标 |
返回生成的 Boss 的生物实体表。插件不可用、配置缺失、生成失败等导致集成无法返回存活 Boss 时,返回 nil。nil 本身不能说明原因。
示例
return {
api_version = 1,
on_right_click = function(context)
local loc = context.prop.current_location
if loc then
local boss = context.prop:spawn_elitemobs_boss("dungeon_guardian.yml", loc.x, loc.y + 1, loc.z)
if boss then
context.log:info("Spawned boss: " .. (boss.name or "unknown"))
else
context.log:warn("Could not spawn boss -- is EliteMobs installed?")
end
end
end
}
prop:open_inventory(player, title, rows)
排队为玩家打开箱子物品栏界面。每个道具只有一个共享物品栏:第一次打开时根据道具 PersistentDataContainer 中保存的内容构建,之后的打开会向每位查看者显示同一个实时物品栏。FMM 会在最后一位查看者关闭它时、道具所在区块卸载时,以及 drop_inventory() 读取它之前,把它保存回道具。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
player | 实体表 | 必填 | 要显示界面的玩家 |
title | 字符串 | 必填 | 标题,支持 & 颜色代码 |
rows | 整数 | 3 | 1 到 6 行;6 行为 54 格,相当于大箱子 |
引用有效且打开操作已排队时返回 true,否则返回 false。创建前会把行数限制在 1 到 6。
大小和标题取自构建该物品栏的那次调用;物品栏打开期间,之后的调用会原样复用它。如果保存的内容所占格子多于请求的行数,物品栏会扩大以容纳它们,因此较小的 rows 值永远不会丢弃已保存的物品。如果无法读取保存的数据,物品栏不会打开。如果道具在操作执行前已被移除,该操作会被跳过。返回值不保证物品栏已经打开。
prop:is_viewing_inventory(player)
返回物品栏监听器是否将玩家追踪为此道具的查看者。不保证玩家实际上仍打开着该界面。
| 参数 | 类型 | 说明 |
|---|---|---|
player | 实体表 | 要检查的玩家 |
返回 true 或 false。
示例:关闭物品栏后播放动画
context.state["task_" .. player.uuid] = context.scheduler:run_repeating(5, 5, function(tick_context)
if not tick_context.prop:is_viewing_inventory(player) then
tick_context.prop:play_animation("close", true, false)
tick_context.scheduler:cancel(tick_context.state["task_" .. player.uuid])
end
end)
prop:place_book(player)
取走玩家主手的成书或书与笔,存到道具中。
| 参数 | 类型 | 说明 |
|---|---|---|
player | 实体表 | 手持书本的玩家 |
引用有效且操作已排队时返回 true。后续主线程操作只在玩家仍持有适合的书、道具还没有书时存入。
prop:read_book(player)
向玩家打开保存的书。
| 参数 | 类型 | 说明 |
|---|---|---|
player | 实体表 | 阅读的玩家 |
引用有效且阅读已排队时返回 true。执行时需要已保存且可反序列化的 WRITTEN_BOOK。保存的 WRITABLE_BOOK 不会打开。
prop:take_book(player)
将保存的书归还玩家背包,并从道具移除。
| 参数 | 类型 | 说明 |
|---|---|---|
player | 实体表 | 接收书本的玩家 |
引用有效且取书已排队时返回 true,不保证有书。
请先确保背包有空位。添加物品前会删除保存记录,放不下的部分不会掉落。place_book 会序列化整个手持堆叠,再扣除一件物品,因此请使用正常的单本书。
prop:has_book()
只检查底层盔甲架的 PDC 是否有书本键,不验证序列化内容。不接受参数。
prop:drop_inventory()
排队保存打开中的物品栏(如果有),并为每位查看者关闭它,然后读取保存的内容,删除其 PDC 记录,并在道具位置掉落物品。截至此刻在打开的物品栏中所做的更改都会包含在内。删除记录后若解码失败,可能没有物品掉落。
不接受参数。底层盔甲架有效且掉落操作已排队时返回 true,不保证存在保存的物品。
prop:drop_book()
排队将保存的书掉落在道具位置。解码前先移除保存记录,因此解码失败可能导致书本丢失。
不接受参数。底层盔甲架有效且操作已排队时返回 true,不保证存在书本。
prop:set_persistent_data(key, value)
在道具盔甲架的 PersistentDataContainer 中保存字符串。正常保存世界和卸载区块时,数据随该盔甲架保留。永久移除道具或换成放置用纸不会转移这些数据。
| 参数 | 类型 | 说明 |
|---|---|---|
key | 字符串 | 有效的 Bukkit 键后缀,可用小写字母、数字和 ._-/。内部保存为 fmm_lua_<key>;非法字符会报错 |
value | 字符串 | 保存值;数字与布尔值请使用 tostring() |
成功返回 true,没有底层盔甲架则返回 false。
prop:get_persistent_data(key)
读取由 set_persistent_data 保存的字符串;键不存在时返回 nil。
| 参数 | 类型 | 说明 |
|---|---|---|
key | 字符串 | 保存时使用的键 |
示例:保存开关状态
return {
api_version = 1,
on_spawn = function(context)
local saved = context.prop:get_persistent_data("active")
context.state.active = saved == "true"
end,
on_right_click = function(context)
context.state.active = not context.state.active
context.prop:set_persistent_data("active", tostring(context.state.active))
end
}
物品效果
装备物品的 context.item API 已退役。请用自定义附魔定义效果,再通过物品的 enchantments: 列表和带命名空间的 ID 关联。
context.event
当前钩子的事件数据,在道具点击、投射物命中和通用区域钩子中可用。on_spawn、on_game_tick、on_destroy 等没有关联事件或玩家的钩子中为 nil。
FMM 点击使用插件自己的取消标志来控制道具默认行为。Lua 封装只公开下列字段和方法。
字段与方法
| 字段或方法 | 类型 | 说明 |
|---|---|---|
event.player | 玩家实体表 | 点击道具或穿越监听区域边界的玩家。用于 on_left_click、on_right_click、on_zone_enter、on_zone_leave。在 on_projectile_hit 中为射出该投射物的生物实体,可能是怪物;射击者不是生物实体时不存在此字段。详见玩家实体方法 |
event.is_cancelled | 布尔 | 构建上下文时的取消状态,调用 cancel() 或 uncancel() 后不刷新 |
event.cancel() | 函数 | 取消事件,阻止默认伤害或交互 |
event.uncancel() | 函数 | 恢复被取消的事件 |
cancel 和 uncancel 忽略所有传入参数,因此 context.event.cancel() 与 context.event:cancel() 相同。本页使用点号,两种形式都可用。
通用区域钩子有玩家但没有取消标志,因此不存在 event.cancel() 和 event.uncancel(),且 event.is_cancelled 为 false。取消道具点击会阻止 FMM 对应的默认操作。
event.is_cancelled 是初始快照。若要在同一钩子后面记住取消状态的变化,请用局部变量保存。
事件表不提供 Bukkit 特有的 target、block、projectile 或 item。需要额外信息时,请使用 context.player、context.event.player、context.projectile(在 on_projectile_hit 中)、player:get_target_entity(range)、context.world:raycast(...) 或附近实体查询。
on_projectile_hit
投射物击中道具的碰撞箱时触发,每个投射物触发一次。
| 上下文值 | 说明 |
|---|---|
context.projectile | 投射物的实体表(箭、三叉戟、雪球等)。仅在此钩子中设置 |
context.event.player | 射出该投射物的生物实体(如有) |
context.player | 射击者为玩家时即为该玩家,否则为 nil |
此钩子在 FMM 处理完命中后运行,而 FMM 对道具的默认投射物处理不做任何事。在这里调用 context.event.cancel() 无法阻止非穿透投射物因命中而被消耗。如果其他插件已经取消了这次命中,此钩子不会触发。
return {
api_version = 1,
on_projectile_hit = function(context)
local shooter = context.player
if shooter then
shooter:send_message("&eYou hit the target!")
end
context.prop:play_animation("hit", false, false)
end
}
示例:取消左键伤害
示例
return {
api_version = 1,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}
示例:检查取消状态
示例
return {
api_version = 1,
on_left_click = function(context)
if context.event and not context.event.is_cancelled then
context.event.cancel()
context.log:info("Damage cancelled!")
end
end
}
scheduler:run_later 和 scheduler:run_repeating 的回调中,context.event 始终为 nil。只能在事件钩子执行期间修改事件。
context.world
这是 FMM/MagmaCore 的世界 API,完整说明见 context.world。
共用页面的 get_block_at、set_block_at、spawn_particle、play_sound、strike_lightning、get_time、set_time、get_nearby_entities、get_nearby_players、spawn_entity、get_highest_block_y、raycast、place_temporary_block、drop_item、spawn_firework 在 FMM 中都可用。MagmaCore 世界 API说明了检测实体和方块的 world:raycast()、临时替换方块的 world:place_temporary_block(),以及自定义烟花的 world:spawn_firework()。EliteMobs Boss 技能从同一基础扩展出使用位置表的 Boss、增援、下落方块、临时方块等操作,详见 EliteMobs 世界与环境。
FMM 特有的世界扩展
FMM 添加三个可选的 EliteMobs 战利品方法。它们始终存在,但没有安装 EliteMobs 时会返回 false,不执行操作。
| 方法 | 说明 |
|---|---|
world:drop_elitemobs_procedural_loot(player, level, location?) | 生成程序化战利品。遵循 EliteMobs 的直接入包设置,溢出物品掉落在玩家位置;否则使用指定位置。程序化掉落关闭时返回 false |
world:drop_elitemobs_random_loot(player, level, location?) | 按指定等级为玩家抽取 EliteMobs 战利品表 |
world:drop_elitemobs_custom_loot(player, file, level, location?) | 掉落指定 EliteMobs 自定义物品。无法解析文件时返回 false |
完整签名见 Lua API 参考。道具生成 Boss 的方法是前述 prop:spawn_elitemobs_boss(...)。
玩家实体方法
玩家表来自 context.player、context.event.player 和 context.world:get_nearby_players()。MagmaCore 通用区域钩子 on_zone_enter 与 on_zone_leave 会将进入或离开的玩家放入前两个字段。
FMM 使用共用页面中的实体、生物实体、玩家和 UI 方法。EliteMobs Boss 技能使用相似但专用于 Boss 的表,见 Boss 与实体。FMM 的完整字段与方法见 MagmaCore Lua 引擎和玩家 UI 方法。player:get_target_entity() 的射线选取、player:get_eye_location()、player:get_look_direction()、向单个玩家显示假方块的 player:send_block_change() 以及 player:reset_block(),详见玩家专用方法。
FMM 特有的实体字段
LuaEntityEnricher 为 FMM 实体表增加以下字段。标志与模型引用是建表时的快照;改变模型绑定后请重新获取实体表。玩家的 has_permission 查询当前权限,is_op 则是快照。
| 字段 | 类型 | 说明 |
|---|---|---|
entity.is_modeled | 布尔 | Bukkit 实体是 ModeledEntity 的底层实体时为 true |
entity.is_prop | 布尔 | 实体是 PropEntity 的底层盔甲架时为 true |
entity.model | 表或 nil | 只在 is_modeled = true 时提供 |
模型表提供以下内容。
| 字段或方法 | 说明 |
|---|---|
model.model_id | 蓝图模型名,例如 "dragon" |
model.is_dynamic | 附着到生物实体的 DynamicEntity 为 true |
model:play_animation(name, blend, loop) | 请求动画;此集成的 blend 和 loop 默认都是 false。true 表示接受请求,不证明已经显示 |
model:stop_animations() | 清空队列,有 idle 时返回该状态,不中断终止死亡状态 |
model:remove() | 立即删除模型实体及其全部骨骼 |
on_right_click = function(context)
local player = context.event and context.event.player
if not player then return end
local target = player:get_target_entity(8)
if target and target.is_modeled then
target.model:play_animation("hurt", true, false)
end
end
EliteMobs 实体字段
安装 EliteMobs 后,FMM 通过其 enricher 提供以下字段。
| 字段 | 类型 | 说明 |
|---|---|---|
entity.is_elite | 布尔 | 实体由 EliteMobs 追踪时为 true |
entity.is_custom_boss | 布尔 | 使用自定义 Boss 配置时为 true |
entity.is_significant_boss | 布尔 | 自定义 Boss 的 healthMultiplier > 1 时为 true,用于排除命名杂兵 |
entity.elite | 表或 nil | 只在 is_elite = true 时提供,包含 level、name、health、max_health、health_multiplier、damage_multiplier、is_custom_boss 和 elite:remove() |
标志和多数 elite 字段是快照,health 与 max_health 在读取时查询。启用 EliteMobs 时,entity:can_receive_hostile_effect(player_uuid) 在共用敌对目标检查之后,还执行 EliteMobs 的魔法目标授权检查。适配器不可用时检查会拒绝,不会把盟友当作独立目标处理。
context.zones
FMM/MagmaCore 的区域 API 见 context.zones。EliteMobs Boss 技能使用另一个带原生区域定义的表,详见 EliteMobs 区域与目标选择。
context.scheduler
FMM 与 EliteMobs NPC 脚本使用 MagmaCore 调度器。run_after、run_every、cancel_task 也是共用调度器的别名,两套名称都可用。Boss 技能通过专用上下文提供相同别名。详见 context.scheduler。
context.state
FMM、EliteMobs Boss 技能与 NPC 脚本共用此状态 API,详见 context.state。
context.log
FMM/MagmaCore 提供 info、warn、error。EliteMobs NPC 使用同一日志器,Boss 技能提供 info、warn、debug。详见 context.log。
context.cooldowns
FMM 与 EliteMobs NPC 使用共用的参数顺序 check_local(key?, duration)。Boss 技能顺序相同,但使用专用存储。详见 context.cooldowns。
| 方法 | 说明 |
|---|---|
local_ready(key?) | 检查本地冷却是否就绪 |
local_remaining(key?) | 返回本地剩余 tick 数,没有则为 0 |
check_local(key?, duration) | 原子地检查并开始本地冷却 |
set_local(duration, key?) | 不检查,直接设置本地冷却 |
global_ready() | 检查脚本所有者共用的全局冷却 |
set_global(duration) | 设置全局冷却 |
普通道具操作请用 context.cooldowns:check_local("my_key", 40)。
运行时模型
每个脚本实例独立运行
道具 scripts: 列表中的每个条目都有独立 Lua 运行时。FMM 注册提供器时发现并验证源码,再为每个绑定在新沙箱中执行缓存源码。修改或添加 Lua 文件后,需要成功重载,新绑定才会使用更改。回调按配置列表顺序执行,解析出的 priority 不会重新排序。移除道具时关闭其脚本实例。
物品附魔有独立生命周期,不会为装备物品创建道具脚本实例。
- 文件级局部变量只属于当前实例。
- 即使使用同一脚本文件,
context.state也在实例之间完全隔离。
调度任务归属
context.scheduler 创建的任务属于创建它的运行时。移除道具时会执行以下步骤。
- 关闭运行时。
- 自动取消所有一次性和重复任务。
- 清除所有区域监听。
只有在道具被永久移除时(被拾取、被破坏、被 /fmm deleteall 删除或被插件移除),才会先运行 on_destroy。当道具所在区块或世界卸载,或 FMM 重载或关闭时,运行时会停止而不调用 on_destroy。区块再次加载时,道具会以新的运行时回来,并再次触发 on_spawn。
冷却范围
共用引擎提供本地方法 local_ready、local_remaining、check_local、set_local,以及全局方法 global_ready、set_global。
| 脚本类型 | 本地范围 | 全局范围 |
|---|---|---|
| 道具脚本 | 每个 ScriptInstance,即道具与脚本文件 | 每个 PropEntity,其所有绑定脚本共用 |
注销道具,或成功重载、禁用时关闭脚本管理器,都会清除道具的全局冷却。重载准备被拒绝时保留当前运行时。冷却按单调经过的时间计时,每个指定 tick 换算成 50 毫秒;调度器延迟遵循服务器 tick,服务器卡顿时两者可能不同。
执行预算
钩子、调度回调和脚本的初始执行都有硬性预算,由 Lua VM 内部在执行过程中限制。
| 限制 | 值 |
|---|---|
| 当前线程 CPU 时间 | 50 毫秒 |
| Lua 指令数 | 250,000 |
先达到的上限会以 Lua 错误终止调用,并禁用脚本实例。消息如下。
Lua instruction budget exceeded (250000 instruction limit)
Lua CPU-time budget exceeded (50ms current-thread CPU limit)
指令计数器能限制 while true do end 这类纯 Lua 循环。时间在每 1,024 条 Lua 指令及调用返回时检查,无法抢占正在执行的阻塞 Java/Bukkit 方法。
CPU 时间不包括服务器线程未获得调度的时段。JVM 无法提供当前线程 CPU 计时时,改用 250 毫秒经过时间,报错为 Lua elapsed-time fallback budget exceeded (250ms fallback; current-thread CPU time unavailable);250,000 条指令上限仍保留。
嵌套调用共用一份预算。钩子调用回调、回调再调用其他函数时,整条链共用 50 毫秒 CPU 和 250,000 条指令。
发现提供器时的执行会在注册前拒绝无效定义。缓存源码会对每个新实例再次执行,使用相同预算。
- 避免无界循环。
on_game_tick每 tick 运行,请保持轻量。- 使用
context.scheduler:run_repeating(...)将工作分散到多个 tick。
完整钩子参考
下表是当前启用的道具钩子。context.event 表示 FMM 取消标志或区域玩家;Lua 按适用情况提供 event.player、event.is_cancelled、event.cancel()、event.uncancel()。
八个有效道具钩子
| 钩子 | 触发时机 | context.event |
|---|---|---|
on_spawn | 道具生成到世界,或随其区块重新加载 | nil |
on_game_tick | 每个服务器 tick,20 TPS 时为 50 毫秒 | nil |
on_destroy | 道具被永久移除(区块卸载或重载时不会触发) | nil |
on_left_click | 玩家左键点击道具 | 默认伤害取消标志 |
on_right_click | 玩家右键点击道具 | 默认交互取消标志 |
on_zone_enter | 玩家进入监听区域 | context.player / context.event.player,不可取消 |
on_zone_leave | 玩家离开监听区域 | context.player / context.event.player,不可取消 |
on_projectile_hit | 投射物击中道具 | 投射物命中事件;event.player 为射击者。参见 on_projectile_hit |
物品效果
手持物品效果请使用自定义附魔。旧的 22 个物品钩子已不是当前运行时接口。
后续阅读
EliteMobs 交通路线
context.world:start_elitemobs_transport(player, route_id) 为玩家启动上下文世界中保存的 EliteMobs 路线。返回值表示请求是否被接受,需要 EliteMobs 和已加载的路线。ID 不包含 .yml。详见交通路线。