跳到主要内容

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_locationlocation 表道具当前的位置,读取时查询,不是固定快照

位置表包含 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布尔truetrue 在当前状态的 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整数31 到 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 会将进入或离开的玩家放入前两个字段。

MagmaCore 实体表

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 创建的任务属于创建它的运行时。移除道具时会执行以下步骤。

  1. 关闭运行时。
  2. 自动取消所有一次性和重复任务。
  3. 清除所有区域监听。

只有在道具被永久移除时(被拾取、被破坏、被 /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。详见交通路线。