跳到主要内容

Lua 脚本:道具与物品 API

本页涵盖 FreeMinecraftModels 道具和物品脚本可用的全部 API:context.propcontext.itemcontext.eventcontext.playercontext.worldcontext.zonescontext.schedulercontext.statecontext.log。如果你是脚本新手,请先阅读 入门


context.prop

prop 表提供道具实体的信息以及控制其动画的方法。每次钩子调用时都会重新构建。

字段

字段类型说明
prop.model_id字符串蓝图模型名称(如 "torch_01"
prop.current_locationlocation 表构建上下文时道具的位置

location 表具有标准字段:xyzworldyawpitch

示例:读取道具信息
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是否与当前动画混合
loop布尔true动画是否循环

若动画被找到并启动则返回 true,否则返回 false

示例
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()

停止道具上所有正在播放的动画。

不接受参数。

示例
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)
-- 被击打时闪烁红色,但不实际受伤
if context.event then
context.event.cancel()
end
context.prop:hurt_visual()
end
}

prop:pickup()

将道具从世界中移除,并在其位置掉落放置用的纸质物品。掉落的物品可以右键点击方块再次放置该道具。

不接受参数。

示例
return {
api_version = 1,

on_right_click = function(context)
-- 让玩家通过右键拾取道具
context.prop:pickup()
end
}

prop:mount(player)

将玩家挂载到道具上第一个可用的挂载点座位。模型必须定义挂载点骨骼。

参数类型说明
player实体表玩家实体表(例如来自 context.event.player
示例
return {
api_version = 1,

on_right_click = function(context)
local player = context.event and context.event.player
if player then
context.prop:mount(player)
end
end
}

prop:dismount(player)

将玩家从道具上的挂载点座位卸下。

参数类型说明
player实体表玩家实体表
示例
return {
api_version = 1,

on_right_click = function(context)
local player = context.event and context.event.player
if player then
-- 切换挂载/下骑
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()

返回该道具的模型中是否定义了挂载点骨骼。

不接受参数。返回 truefalse

示例
return {
api_version = 1,

on_right_click = function(context)
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 的生物实体表;若未安装 EliteMobs 或 Boss 文件不存在,则返回 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)

为玩家打开一个持久化的箱子物品栏 GUI。物品栏关闭时内容会保存到道具的 PersistentDataContainer,下次打开时恢复。

参数类型默认说明
player实体表必填要展示物品栏的玩家
title字符串必填物品栏标题(支持 & 颜色代码)
rowsint3行数(1-6,其中 6 = 54 格 = 大箱子)

若物品栏打开成功返回 true,否则返回 false


prop:is_viewing_inventory(player)

返回指定玩家当前是否打开了该道具的物品栏。

参数类型说明
player实体表要检查的玩家

返回 truefalse

示例:物品栏关闭时播放关闭动画
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


prop:take_book(player)

将存储的书归还到玩家背包,并从道具上移除。

参数类型说明
player实体表接收书本的玩家

成功时返回 true


prop:has_book()

返回该道具上是否存有书。不接受参数。


prop:drop_inventory()

将所有已存储的物品栏内容作为物品实体掉落在道具位置,然后清空存储数据。会自动关闭任何正在查看该物品栏的玩家。

不接受参数。成功时返回 true


prop:drop_book()

将存储的书作为物品实体掉落在道具位置,并清空存储的书数据。

不接受参数。成功时返回 true


prop:set_persistent_data(key, value)

将一个字符串值存储到道具的盔甲架 PersistentDataContainer 中。该数据可在服务器重启和区块卸载后保留。

参数类型说明
key字符串唯一键名(内部以 fmm_lua_<key> 存储)
value字符串要存储的值。数字和布尔值请使用 tostring()

成功时返回 true;若道具没有底层盔甲架则返回 false


prop:get_persistent_data(key)

获取之前用 set_persistent_data 存储的字符串值。若键未设置则返回 nil

参数类型说明
key字符串set_persistent_data 中使用的键名
示例:持久化开关状态
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

item 表仅在物品脚本中可用(道具脚本无此表)。它提供自定义物品的信息以及操作该物品的方法。该表在每次钩子调用时重新构建。

字段

字段类型说明
item.id字符串物品类型 ID(来自 YML 配置中的 fmm_item_id

item:material()

以字符串形式返回物品的材质名称(如 "DIAMOND_SWORD""STICK")。


item:get_amount() / item:set_amount(n)

获取或设置物品的堆叠数量。

参数类型说明
nint新的堆叠数量

item:consume(n)

将物品的堆叠数量减少 n(默认 1)。若结果数量小于等于 0,物品会从玩家背包中移除。

参数类型默认说明
nint1要消耗的数量

item:get_uses() / item:set_uses(n)

获取或设置存储在物品 PersistentDataContainer 中的自定义使用计数。这与原版耐久度无关,可用于实现自定义耐久或充能系统。

参数类型说明
nint新的使用次数

item:get_name() / item:set_name(s)

获取或设置物品的显示名称。通过 & 支持颜色代码。

参数类型说明
s字符串新的显示名称(如 "&b&lFrost Sword"

item:get_lore() / item:set_lore(table)

获取或设置物品的 Lore。get_lore() 返回一个字符串表(每行一个)。set_lore() 接受字符串表。

参数类型说明
table字符串数组,每个对应一行 Lore
示例:跟踪使用次数的物品脚本
return {
api_version = 1,

on_right_click = function(context)
local uses = context.item:get_uses()
if uses <= 0 then
context.player:send_message("&cThis item is out of charges!")
return
end
context.item:set_uses(uses - 1)
context.player:send_message("&aUsed! Charges remaining: " .. (uses - 1))
end
}

item:get_durability()

返回一个带有 currentmax 字段的表,代表物品的原版耐久度;若物品没有耐久度条则返回 nil

示例
local dur = context.item:get_durability()
if dur then
context.player:send_message("Durability: " .. dur.current .. "/" .. dur.max)
end

item:get_durability_percentage()

0.01.0 的分数形式返回剩余耐久度;若物品没有耐久度条则返回 nil


item:use_durability(amount, can_break)

按固定数量减少物品的原版耐久度。

参数类型默认说明
amountint必填要消耗多少耐久点
can_break布尔false若为 true,耐久耗尽时物品被销毁。若为 false,耐久最低停在 1

item:use_durability_percentage(fraction, can_break)

按最大耐久度的百分比减少物品的原版耐久度。

参数类型默认说明
fraction数字必填消耗的最大耐久比例(如 0.1 = 10%)
can_break布尔false若为 true,耐久耗尽时物品被销毁。若为 false,耐久最低停在 1

context.event

当前钩子的事件数据。在道具脚本与物品脚本的点击、战斗、交互和通用区域钩子中可用。在没有关联事件也没有玩家行为者的钩子(on_spawnon_game_tickon_destroyon_equip)中返回 nil

下方的钩子参考表会标注底层的 Bukkit 事件类型以供参考。但 Lua 封装仍然只暴露这里列出的字段和方法。

字段与方法

字段或方法类型说明
event.player玩家实体表触发事件或跨越了被监视的通用区域边界的玩家。在道具的 on_left_clickon_right_clickon_zone_enteron_zone_leave 中可用,以及在由玩家引发的物品钩子中可用。完整字段与方法见玩家实体方法
event.is_cancelled布尔构建上下文时的取消状态。调用 cancel()uncancel() 之后该字段不会刷新。
event.cancel()函数取消事件(如阻止伤害或交互)
event.uncancel()函数取消对先前取消的事件的取消
点号或冒号,两种都可以

canceluncancel 会忽略传给它们的任何内容,因此 context.event.cancel()context.event:cancel() 行为完全一致。FMM 附带的预制脚本使用冒号形式;本页使用点号形式。两者没有对错之分。

信息

并非所有事件都可取消。若底层 Bukkit 事件未实现 Cancellable,或者该钩子是背后没有 Bukkit 事件的通用区域钩子,则 event.cancel()event.uncancel() 将不存在,且 event.is_cancelled 始终为 false

请把 event.is_cancelled 当作初始状态的快照。如果你自己的脚本调用了 event.cancel()event.uncancel(),而你需要在同一个钩子的后续代码中记住这个变化,请自行保留一个局部标志。

当前的 FMM 事件表不会暴露 targetblockprojectileitem 等 Bukkit 特有字段。当你需要额外的上下文时,请使用 context.playercontext.event.playerplayer:get_target_entity(range)context.world:raycast(...) 或附近实体查询。

示例:让道具无敌

示例
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_laterscheduler:run_repeating)内部,context.event 始终为 nil。事件修改只能在事件钩子自身中进行。


context.world

这是 FreeMinecraftModels/MagmaCore 的 world API。完整参考见 context.world

信息

全局页面上记录的所有方法(get_block_atset_block_atspawn_particleplay_soundstrike_lightningget_timeset_timeget_nearby_entitiesget_nearby_playersspawn_entityget_highest_block_yraycastplace_temporary_blockdrop_itemspawn_firework)在 FMM 中均可用。world:raycast()(投射射线并检测命中实体/方块)、world:place_temporary_block()(临时替换方块)与 world:spawn_firework()(生成可自定义颜色与形状的烟花火箭)的完整细节见 MagmaCore world API。EliteMobs 的 Boss 技能以同一个 world 基础表为起点,并额外提供 Boss 专用的位置表方法,用于生成 Boss、增援、下落方块、临时方块等——详见 EliteMobs 世界与环境

FMM 特有的 world 扩展

FreeMinecraftModels 在 context.world 之上叠加了三个可选的 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.playercontext.event.playercontext.world:get_nearby_players() 返回。通用的 MagmaCore on_zone_enter / on_zone_leave 钩子会把 context.playercontext.event.player 设为进入或离开的玩家。

MagmaCore 实体表

全局页面上记录的实体表、生物实体方法、玩家专有方法以及玩家 UI 方法,就是 FMM 所使用的 MagmaCore 表。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 特有的实体字段

在 FMM 脚本内构建的每个实体表都会自动获得这些额外字段(通过 FMM 的 LuaEntityEnricher):

字段类型说明
entity.is_modeled布尔若该 Bukkit 实体是某个 ModeledEntity 的底层实体则为 true
entity.is_prop布尔若该实体是支撑 PropEntity 的盔甲架则为 true
entity.model表或 nil仅在 is_modeled = true 时填充(见下方)

entity.model 存在时,它暴露:

字段 / 方法说明
model.model_id蓝图模型名称(如 "dragon"
model.is_dynamic若是 DynamicEntity(附着到生物实体)则为 true
model:play_animation(name, blend, loop)播放指定名称的动画。成功时返回 true
model:stop_animations()停止所有当前动画
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 会将请求转发到 EliteMobs 的 enricher,因此相同的实体表也会暴露:

字段类型说明
entity.is_elite布尔若实体被 EliteMobs 追踪则为 true
entity.is_custom_boss布尔若是自定义 Boss 配置则为 true
entity.is_significant_boss布尔healthMultiplier > 1 的自定义 Boss 为 true(过滤掉杂兵命名怪物)
entity.elite表或 nil仅在 is_elite = true 时填充。包含 levelnamehealthmax_healthhealth_multiplierdamage_multiplieris_custom_boss,以及 elite:remove()

context.zones

这是 FreeMinecraftModels/MagmaCore 的 zones API。完整参考见 context.zones。EliteMobs 的 Boss 技能使用的是另一个 context.zones 表,带有 EliteMobs 原生的区域定义;详见 EliteMobs 区域与目标选择


context.scheduler

这里记录的 scheduler API 是 FreeMinecraftModels 脚本和 EliteMobs NPC 脚本所使用的 MagmaCore 调度器。EliteMobs 风格的名称(run_afterrun_everycancel_task)是这个共享调度器上的别名,因此两种命名风格都可以使用。Boss 技能通过其 Boss 专属上下文暴露相同的别名。完整的 FMM 参考见 context.scheduler


context.state

state API 由 FreeMinecraftModels 脚本、EliteMobs Boss 技能和 EliteMobs NPC 脚本共享。完整参考见 context.state


context.log

这里记录的日志 API 是 FreeMinecraftModels/MagmaCore 的日志器(infowarnerror)。EliteMobs NPC 脚本使用同一个日志器;EliteMobs 的 Boss 技能暴露的是 infowarndebug。完整参考见 context.log


context.cooldowns

这里记录的冷却 API 是 FreeMinecraftModels 脚本和 EliteMobs NPC 脚本共用的 MagmaCore/FMM 参数顺序:check_local(key?, duration)。EliteMobs 的 Boss 技能使用相同的参数顺序,但底层存储是 Boss 专属的。完整参考见 context.cooldowns

方法说明
local_ready(key?)检查本地冷却是否就绪。
local_remaining(key?)返回本地冷却剩余的刻数,若无则为 0
check_local(key?, duration)原子地检查并启动一个本地冷却。
set_local(duration, key?)直接设置本地冷却,不做检查。
global_ready()检查脚本拥有者的共享全局冷却。
set_global(duration)设置脚本拥有者的共享全局冷却。

普通的道具或物品动作冷却请使用 context.cooldowns:check_local("my_key", 40)


运行时模型

每个脚本实例独立运行时

每个附加了脚本的道具实体都获得自己独立的 Lua 运行时实例。当道具被生成时,FMM 加载 Lua 源代码,在新沙箱环境中求值并保存返回的表。道具被移除时,运行时也随之关闭。

对于物品脚本,每个 (玩家, itemId) 对会创建一个运行时。当玩家装备某个自定义物品时,FMM 为该玩家与该物品类型创建一个脚本实例。当物品被卸下时,运行时关闭。

这意味着:

  • 在文件作用域声明的局部变量对该脚本实例私有。
  • 即使两个实例共用同一脚本文件,它们的 context.state 也是完全隔离的。

调度任务归属

通过 context.scheduler 创建的所有任务都归属于创建它们的运行时。当道具被移除时:

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

冷却作用域

共享的脚本引擎暴露本地冷却辅助方法(local_readylocal_remainingcheck_localset_local)和全局冷却辅助方法(global_readyset_global)。FMM 对这些存储的作用域如下:

脚本类型local 存储作用域global 存储作用域
道具脚本每个 ScriptInstance(道具 + 脚本文件)每个 PropEntity(绑定到该道具的所有脚本共享)
物品脚本每个 (player, itemId, scriptFile) 三元组 —— 即使脚本实例在物品离开活动栏位时被销毁,冷却也会跨重新装备保留每个玩家(该玩家运行的所有 FMM 物品脚本共享)

由于物品脚本在每次装备/卸下周期都会被销毁并重建,物品冷却被保存在以玩家 UUID 为键的静态映射中,而不是存活于 ScriptInstance 上。这就是为什么把物品从快捷栏换出再换回,物品冷却仍然有效的原因。

执行预算

每次钩子调用、每次调度回调,以及脚本文件本身的初次求值,都在一个硬性执行预算下运行。该预算在 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 无法冻结服务器。

预算中与时间相关的一半是以当前线程 CPU 时间计量的,而非挂钟时间,因此当服务器线程被调度让出时,脚本不会被计入这段时间。在无法获取当前线程 CPU 计时的 JVM 上,MagmaCore 会退回到一个刻意更宽松的 250 毫秒实耗时间限制(Lua elapsed-time fallback budget exceeded (250ms fallback; current-thread CPU time unavailable)),同时保留同样的 250,000 指令上限,这样无论哪种情况,不终止的脚本都仍然受到约束。

嵌套调用共享同一份预算:如果一个钩子调用了一个回调,而该回调又调用了另一个,整条调用链会被作为单份 50 毫秒 CPU / 250,000 指令的额度来计量,而不是每一层各有一份。

在脚本文件的初次求值期间还不存在实例,因此该定义会被直接拒绝、根本不予注册,而不是被禁用。

为保持在预算之内:

  • 避免钩子内的无界循环。
  • on_game_tick 处理器保持轻量 —— 它每一刻都会运行。
  • 使用 context.scheduler:run_repeating(...) 把工作分摊到多个刻。

完整钩子参考

本表列出道具脚本与物品脚本中可用的所有钩子。

道具钩子(7)

钩子触发时机context.event
on_spawn道具被生成到世界中nil
on_game_tick每一服务器刻(50ms)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;不可取消)
Reserved Prop Hook

当前的脚本校验器会接受道具脚本中的 on_projectile_hit,但当前的运行时尚未把抛射物命中派发给道具脚本。若需要与脚本化物品绑定的抛射物行为,请使用物品的 on_projectile_hit;若需要在插件侧处理模型化实体的抛射物,请使用 Bukkit 的 ModeledEntityHitByProjectileEvent API。

物品钩子(22)

钩子类别触发时机context.event
on_attack_entity战斗玩家攻击实体伤害事件
on_kill_entity战斗玩家击杀实体死亡事件
on_take_damage战斗玩家受到伤害伤害事件
on_shield_block战斗玩家使用盾牌格挡伤害事件
on_shoot_bow战斗玩家射出弓弓射出事件
on_projectile_hit战斗玩家的抛射物命中抛射物命中事件
on_projectile_launch战斗玩家发射抛射物发射事件
on_right_click交互玩家右键点击交互事件
on_left_click交互玩家左键点击交互事件
on_shift_right_click交互玩家 Shift+右键交互事件
on_shift_left_click交互玩家 Shift+左键交互事件
on_interact_entity交互玩家右键点击实体实体交互事件
on_equip装备物品进入活动栏位nil
on_unequip装备物品离开活动栏位nil
on_swap_hands装备在主副手之间切换切换事件
on_drop装备玩家丢弃物品丢弃事件
on_break_block工具玩家破坏方块方块破坏事件
on_consume工具玩家食用物品食用事件
on_item_damage工具物品受到耐久损耗物品损耗事件
on_fish工具玩家使用钓鱼竿钓鱼事件
on_death工具玩家在装备物品时死亡死亡事件
on_game_tick生命周期装备期间的每一刻nil

后续阅读

  • 示例与模式 —— 道具与物品的完整可工作脚本及讲解
  • 故障排查 —— 常见问题、调试技巧和质量检查清单
  • 入门 —— 文件结构、钩子、首个脚本的步步讲解

/fmm reload 会在重建前取消脚本拥有的任务和冷却。共享任务包括 run_afterrun_everycancel_task;世界提供 raycastplace_temporary_blockdrop_item