MagmaCore Lua 脚本引擎
MagmaCore 提供 EliteMobs Boss 能力、NPC 脚本、FreeMinecraftModels 道具和自定义附魔效果使用的 Lua 沙箱及 ScriptInstance 运行时。它们共享文件验证和实例管理,但可用钩子、上下文表、冷却作用域和生命周期取决于提供方。附魔查询还有受限上下文和提供方专属的额度,请参阅下方的附魔参考。
FreeMinecraftModels 道具和 EliteMobs NPC 直接继承通用上下文表(context.world、context.zones、context.scheduler、context.cooldowns、context.log、context.event、context.player、context.state),并添加 context.prop 或 context.npc。Boss 能力使用同一运行时,但提供若干表的 Boss 专属变体(context.world、context.zones、context.cooldowns、context.scheduler、context.log、context.event),以及 context.players、context.entities、context.vectors、context.settings 和 context.script 等专属辅助表。
目前,下列插件使用此引擎:
- EliteMobs —— 自定义 Boss 的 Lua 能力(
on_boss_damaged_by_player、on_enter_combat等),以及处理交互和接近行为的 NPC Lua 脚本(on_npc_interact、on_npc_proximity_enter等)。 - FreeMinecraftModels —— 道具的 Lua 脚本(
on_right_click、on_left_click等),以及自定义物品的附魔效果。
本页介绍 FMM 使用的通用 MagmaCore 功能,并说明 EliteMobs Boss 和 NPC 变体的差异。Boss 的准确上下文请查阅 EliteMobs Lua 页面;NPC 请查阅 NPC Lua 脚本。
极简 Lua 入门
如果你完全没接触过 Lua,下面是为任意 Nightbreak 插件编写脚本时所需的最小语法。
变量
使用 local 来存储一个值:
local cooldown_key = "fire_burst"
local damage_multiplier = 1.5
local 表示该变量只属于当前文件或代码块。
函数
函数是可复用的逻辑块:
local function warn_player(player)
player:send_message("&cMove!")
end
之后可以调用它:
warn_player(some_player)
if 判断
仅在某些条件下需要执行时使用 if:
if context.player == nil then
return
end
意思是“如果当前钩子没有玩家,则在这里停止”。
nil
nil 表示“没有值”。它是 Lua 中表示“什么都没有”的方式。
你常会用以下方式检查 nil:
if context.event ~= nil then
-- do something with the event
end
~= 表示“不等于”。
表
Lua 使用表来完成多种工作:
- 列表
- 带命名键的对象
- 最终返回的脚本定义
带命名键的表示例:
local particle = {
particle = "FLAME",
amount = 1,
speed = 0.05
}
返回脚本定义
在每个脚本文件的末尾,你需要返回一个表:
return {
api_version = 1,
on_spawn = function(context)
end
}
返回的表就是脚本定义。加载器先执行文件进行验证,再为每个运行时实例重新执行保存的源码。因此,文件局部变量属于各自实例,不会由使用同一文件的所有实体共享。编辑文件不会改变已加载定义或运行中的实例;请使用提供方插件的流程重新加载。
注释
使用 -- 给人类读者写注释:
-- This cooldown stops the attack from firing every hit
context.cooldowns:set_local(60, "fire_burst")
Lua 沙箱
所有 Lua 脚本都在沙箱化的 LuaJ 环境中运行。可能访问文件系统或 Java 运行时的多个全局变量已被移除。所有使用 MagmaCore 的插件遵循相同的沙箱规则。
已移除的全局变量
以下标准 Lua 全局变量被设为 nil 且无法使用:
| 已移除项 | 原因 |
|---|---|
debug | 暴露 VM 内部状态 |
dofile | 文件系统访问 |
io | 文件系统访问 |
load | 任意代码加载 |
loadfile | 文件系统访问 |
luajava | 直接的 Java 类访问 |
module | 模块系统(不需要) |
os | 操作系统访问 |
package | 模块系统(不需要) |
require | 模块系统 / 文件系统访问 |
可用的标准库
LuaJ 标准环境提供以下常用 Lua 功能:
| 类别 | 函数 |
|---|---|
| 数学 | math.abs、math.ceil、math.floor、math.max、math.min、math.random、math.sin、math.cos、math.sqrt、math.pi,以及所有其他 math.* 函数 |
| 字符串 | string.byte、string.char、string.find、string.format、string.gsub、string.len、string.lower、string.match、string.rep、string.sub、string.upper,以及所有其他 string.* 函数 |
| 表 | table.insert、table.remove、table.sort、table.concat,以及所有其他 table.* 函数 |
| 迭代器 | pairs、ipairs、next |
| 类型 | type、tostring、tonumber、select、unpack |
| 错误处理 | pcall、xpcall、error、assert |
| 其他 | print、rawget、rawset、rawequal、rawlen、setmetatable、getmetatable |
os 库不可用os 已完全移除。通用接口用 context.world:get_time() 读取世界时间,Boss 能力也通过世界表的共享基类获得它。要计算经过时间,可在 on_game_tick 中维护 context.state 的 tick 计数器。
print 会写入服务器控制台,但建议使用 context.log:info(msg)。每个接口都会暴露 context.log:FreeMinecraftModels 脚本和 EliteMobs NPC 脚本暴露 info、warn 和 error;EliteMobs Boss 能力暴露 info、warn 和 debug。
文件契约
每个 Lua 脚本都必须 return 一个表。该表的格式是严格的。
必填与可选的顶层字段
| 字段 | 必填 | 类型 | 说明 |
|---|---|---|---|
api_version | 是 | number | 当前必须为 1 |
priority | 否 | number | 默认 0,转换为整数。EliteMobs Boss 和 NPC 先运行较小值;FMM 验证该字段,但道具按 scripts: 顺序执行 |
| 支持的钩子键 | 否 | function | 必须使用插件支持的准确钩子名 |
验证规则
- 文件必须返回一个表。
api_version是必填项,且目前必须为1。- 若存在
priority,必须是数字。 - 其他每个顶层键都必须是受支持的钩子名。
- 每个钩子键都必须指向一个函数。
- 未知的顶层键会被拒绝。
验证器在所有 MagmaCore 脚本中接受 priority。EliteMobs 用它排序 Boss 和 NPC 脚本,FMM 道具则按 scripts: 列表顺序运行。自定义附魔使用提供方的目录以及动作和查询生命周期。
脚本文件放在哪里
各提供方选择自己的目录。通用引擎递归查找 .lua,排除已归档配置路径,先按不区分大小写的规范化绝对路径排序,再按准确路径排序。对于每个转成小写的文件名,只选择第一个,并记录内容与选中文件不同的被跳过重复文件。选中的文件验证失败也不会改用重复文件。注册查找保留所选文件的实际拼写,引用时请使用该拼写。Boss 能力和附魔目录有自己的发现规则,共用验证器不代表目录可以互换。
脚本是按文件名(而不是路径)作为键的。位于不同子文件夹中的两个 door.lua 是同一个键,最终只有其中一个会被注册。请给每个脚本取一个各不相同的文件名。
辅助函数与本地常量应放在最终 return 之上,而不应放在返回表内部,除非它们本身就是钩子。
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)
do_something(context)
end
}
常见钩子名称
这些名称用于多个运行时,但每种脚本只启用适用于其所有者的钩子。FMM 道具使用以下全部钩子;Boss 使用专属列表;NPC 使用 on_spawn、on_remove、on_game_tick、on_npc_interact、on_npc_proximity_enter、on_npc_proximity_leave、on_zone_enter 和 on_zone_leave。
| 钩子 | 触发时机 |
|---|---|
on_spawn | 由 FMM 道具、EliteMobs Boss 或 NPC 的插件生成流程触发;仅构造 ScriptInstance 不会触发 |
on_game_tick | 已初始化的实例处于活动且未暂停状态时,每个服务器 tick 调用 |
on_zone_enter | 当运行时支持的区域报告进入事件时调用。FMM 与 EliteMobs NPC 的区域监视是基于玩家的;EliteMobs 顶层 Boss 区域钩子来自 EliteScript 区域事件。 |
on_zone_leave | 当运行时支持的区域报告离开事件时调用。FMM 与 EliteMobs NPC 的区域监视是基于玩家的;EliteMobs 顶层 Boss 区域钩子来自 EliteScript 区域事件。 |
方法语法:: 与 .
在 Lua 中,object:method(arg) 是 object.method(object, arg) 的简写。MagmaCore 的上下文表方法会剥离那个隐式的第一个参数,因此对于 context.log.info 这类方法,两种形式都可以工作:
context.log:info("hello")
context.log.info("hello") -- same thing
em 下的全局辅助函数是普通函数,请用 . 调用:em.create_location(...)、em.zone.create_sphere_zone(...) 和 em.location.is_protected(...)。EliteMobs 的事件修改函数 context.event.set_damage_amount(...) 和 context.event.multiply_damage_amount(...) 也必须用 .。本文档对上下文表方法使用 :,对普通函数使用 .。
执行预算
每一次钩子调用、每一次调度回调,以及脚本文件本身的初次求值,都在一个硬性执行预算下运行。该预算在 Lua VM 内部强制执行,因此它在你的代码仍在运行时就已生效,而不是事后才检查时钟。
普通运行时使用以下限制:
| 限制 | 取值 |
|---|---|
| 当前线程的 CPU 时间 | 50 毫秒 |
| 已执行的 Lua 指令数 | 250,000 |
超限会抛出 Lua 错误。指令数逐条检查,时钟每 1,024 条指令及调用返回后检查。它不能打断正在执行的 Java/Bukkit 方法,50 毫秒 CPU 时间也不是 50 毫秒实际经过时间的期限。钩子或回调中的未捕获错误会关闭实例;初次验证时则拒绝定义。无法测量当前线程 CPU 时间时,改用 250 毫秒经过时间及相同指令上限。附魔查询批次可能使用更小的额度。
VM 在执行 Lua 指令时检查预算,因此 while true do end 会被中止。它仍可能耗尽服务器线程的 CPU 额度,并导致脚本被禁用;不要因为有这层保护就编写无界循环。
嵌套调用共享同一份预算:整条调用链共用50毫秒 CPU 时间和250,000条指令,每次调用没有独立额度。
为保持在预算内:
- 避免在钩子内使用无界循环。
- 让
on_game_tick处理函数保持轻量 —— 它们每个 tick 都会运行。 - 使用
context.scheduler:run_repeating(...)将工作分散到多个 tick。 - 用
context.cooldowns或合理间隔限制高开销处理。
钩子或调度回调中的未捕获错误会关闭实例并取消其所属工作。修复并重新加载定义,然后由提供方创建新实例。仅保存文件无法修复已关闭的实例。附魔提供方也有自己的失败抑制规则。
context.state
一个在脚本实例的整个生命周期内持续存在的普通 Lua 表。用它来存储标志、计数器、任务 ID、开关状态以及任何需要在钩子之间共享的数据。
return {
api_version = 1,
on_spawn = function(context)
context.state.is_open = false
context.state.click_count = 0
context.state.task_id = nil
end,
on_right_click = function(context)
context.state.click_count = (context.state.click_count or 0) + 1
end
}
context.state 在实例的钩子间共享数据,只存于内存,不能跨重载或重启保存。其他表按需构建,可能包含快照。FMM 缓存 context.prop,但 current_location 等字段按需读取。要获得当前值,请重新读取动态字段;保存到变量中的表不会自行更新。给动态字段赋值会遮蔽其 getter。
context.log
所有脚本都可使用控制台日志。通用 log.info 带 [Lua] 前缀;需要识别来源时,请自行加入脚本标签。错误诊断会标明文件,普通日志不会自动添加文件名。FMM 和 NPC 提供 info、warn、error;Boss 提供 info、warn、debug。
| 方法 | 说明 |
|---|---|
log:info(message) | 信息性消息 |
log:warn(message) | 警告消息 |
log:error(message) | 警告级别的消息(以 WARN 级别记录,与 log:warn 相同) |
EliteMobs 的 context.log 注册的是 debug 而不是 error。请使用 log:debug(message) 输出调试信息(会以 info 级别加上 debug 前缀显示)。
context.log:info("Script loaded!")
context.log:warn("Something unexpected happened")
-- Generic/FMM/NPC logger:
context.log:error("Critical failure in zone setup")
-- EliteMobs boss logger:
context.log:debug("Boss debug detail")
context.cooldowns
本地冷却和通用全局冷却将每个配置 tick 换算为单调时钟的 50 毫秒,包括暂停和低 TPS 期间。Boss 全局冷却则按服务器 tick 计数。共有两类作用域:
- 本地冷却使用所有者存储中的准确字符串键,作用域见下文。
nil选择默认键,即__lua:加脚本文件名。 - 全局冷却在同一所有者实体上的所有脚本之间共享(例如同一道具上的所有脚本,或同一 Boss 上的所有 Lua 能力)。
cooldowns:check_local(key?, duration)
检查本地键,若就绪则启动并返回 true,否则返回 false。这是一次同步 Lua 调用,不检查或启动全局冷却。该存储不是独立的跨线程锁 API。
| 参数 | 类型 | 说明 |
|---|---|---|
key | string(可选) | 冷却标识符。nil 选择默认键 |
duration | int | 冷却时长,单位为 tick(20 = 1 秒) |
on_right_click = function(context)
-- Only allow this action once every 3 seconds
if not context.cooldowns:check_local("interact", 60) then
return
end
-- ... do the action
end
Boss、NPC 和 FMM 都使用 check_local(key?, duration)。默认键需写为 check_local(nil, 60);check_local(60) 不表示默认键的 60 tick 冷却。早期实验性 NPC 版本使用 check_local(duration, key?),请更新旧脚本的顺序。
cooldowns:local_ready(key?)
若本地冷却已结束(或从未设置过),返回 true;若仍在进行中,返回 false。
cooldowns:local_remaining(key?)
返回本地冷却剩余的 tick 数,若已就绪则返回 0。
cooldowns:set_local(duration, key?)
设置本地冷却,不检查是否已经处于冷却状态。用于无条件重置冷却。
| 参数 | 类型 | 说明 |
|---|---|---|
duration | int | 时长(tick)。传入 0 或负数表示清除。 |
key | string(可选) | 冷却标识符。nil 选择默认键 |
cooldowns:global_ready()
若全局冷却(在同一实体上的所有脚本之间共享)已结束,返回 true。
cooldowns:set_global(duration)
设置全局冷却。通用表中零或负数会清除它。Boss 全局冷却保留较晚的到期时间:较短的时长永远不会缩短正在生效的全局冷却,零或负数时长不起作用。设置本地冷却不会自动设置全局冷却,反之亦然。
| 参数 | 类型 | 说明 |
|---|---|---|
duration | int | 时长(tick) |
全局冷却的“所有者”取决于脚本类型:
- EliteMobs Lua 能力 —— 按
EliteEntity共享(每个 Boss 一个桶)。global_ready()/set_global()使用 Boss 内置的能力冷却系统;本地冷却也会在同一 Boss 的所有能力之间共享。 - EliteMobs NPC 脚本 —— 通过 NPC 的单个适配器共享全局冷却和显式本地键。两个脚本用同一本地键时会相互影响;省略键仍按文件区分。
- FMM 道具脚本 —— 全局按已放置道具共享;各脚本有独立适配器和本地存储。
- 附魔动作 —— 全局在提供方内按玩家共享;本地属于玩家和附魔 ID,可跨装备交换和动作结束保留。参阅自定义附魔。
context.scheduler
调度器运行延迟和重复任务。通过它创建的任务属于实例,在实例关闭时取消。这不会撤销生成实体、世界编辑或其他 UI、效果辅助方法创建的计时器。暂停时,已到期的单次任务等待恢复,重复任务跳过暂停期间的调用。
本节使用 MagmaCore 的调度器方法名(run_later、run_repeating、cancel)。EliteMobs 的名称(run_after(ticks, callback)、run_every(interval, callback)、cancel_task(taskId))在每一个接口上都作为别名被接受,因此两种写法在任何脚本中都可用。
scheduler:run_later(ticks, callback)
在延迟后运行一次回调。返回一个数字任务 ID。
| 参数 | 类型 | 说明 |
|---|---|---|
ticks | int | 延迟时长,单位为服务器 tick(20 tick = 1 秒) |
callback | function | 延迟结束时以全新的 context 调用 |
context.scheduler:run_later(40, function(delayed_context)
delayed_context.log:info("2 seconds have passed!")
end)
scheduler:run_repeating(delay, interval, callback)
按固定间隔重复运行回调。返回一个数字任务 ID。
| 参数 | 类型 | 说明 |
|---|---|---|
delay | int | 首次运行前的初始延迟 tick 数 |
interval | int | 之后每次运行之间的 tick 间隔 |
callback | function | 每次间隔以全新的 context 调用 |
context.state.particle_task = context.scheduler:run_repeating(0, 20, function(tick_context)
tick_context.log:info("Another second passed!")
end)
scheduler:cancel(taskId)
按 ID 取消本实例所属的任务。未知 ID 或其他实例的 ID 不产生效果。
| 参数 | 类型 | 说明 |
|---|---|---|
taskId | int | run_later 或 run_repeating 返回的任务 ID |
实例关闭会自动取消所属任务。如果交互或战斗阶段结束后实例仍存在,应更早取消,例如在 Boss 的 on_exit_combat 中。两组调度器名称均可用。请使用提供方的生命周期钩子;因错误关闭实例时,不保证执行自定义清理钩子。
回调接收不含原始事件或执行者的新上下文。请使用该参数,不要使用外层上下文。如后续需要原玩家,在钩子中保存 UUID,之后重新查找并确认可用。保留的外层上下文还可能在原事件结束后才解析延迟事件字段。
context.world
世界表提供查询和操作方法,首次构建时捕获所有者当前的世界。保存该表不会使它跟随之后的世界变化。
这里介绍 FMM 道具和 NPC 使用的共享表,包括造成伤害的 strike_lightning(...)。FMM 添加可选的 EliteMobs 运输和战利品辅助方法;参阅 FMM 的世界表扩展。EliteMobs 在共享表上添加检查区块加载状态的视觉闪电、NPC 和 Boss 生成功能。Boss 能力还会覆盖或添加专属方法;参阅 EliteMobs 世界与环境。
在通用接口上,context.world 同时接受坐标式调用(例如 strike_lightning(x, y, z)、spawn_particle(name, x, y, z, ...))和位置表式重载(例如 strike_lightning_at_location(loc)、spawn_particle_at_location(loc, ...))。EliteMobs Boss 能力也支持这两大类写法,但部分位置表方法带有 Boss 专属行为。
共享位置参数必须有 x、y、z。省略或无法解析的世界使用该表的世界。共享的位置式粒子仍使用捕获的世界,请传入同世界的位置。Boss 专用转换器和 em.location.* 查询遵循各自参考中的规则。
完整世界表参考还介绍实体查找、可通行性及保护查询、彩色粉尘、地形冲击和位置表重载。
world.name
包含世界名称的字符串字段。
world:get_block_at(x, y, z)
返回 "stone"、"air" 等小写材质名。目标区块未加载时返回 "air",不会强制加载。
| 参数 | 类型 | 说明 |
|---|---|---|
x | int | 方块 X 坐标 |
y | int | 方块 Y 坐标 |
z | int | 方块 Z 坐标 |
world:set_block_at(x, y, z, material)
在主线程设置方块,避免强制加载区块。
| 参数 | 类型 | 说明 |
|---|---|---|
x | int | 方块 X 坐标 |
y | int | 方块 Y 坐标 |
z | int | 方块 Z 坐标 |
material | string | Bukkit Material 名称,小写(例如 "stone"、"air"、"oak_planks") |
材质有效且修改已入队时返回 true,材质无效返回 false。任务执行时区块未加载会跳过修改。
此方法不自动检查区域保护。请使用方块材质,并先执行相关保护查询。接受请求不证明方块已改变。
world:spawn_particle(particle, x, y, z, count, dx, dy, dz, speed)
在某位置生成粒子。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
particle | string | 必填 | Bukkit Particle 枚举名;查找时转为大写,例如 "FLAME"、"HAPPY_VILLAGER" |
x | number | 必填 | X 坐标 |
y | number | 必填 | Y 坐标 |
z | number | 必填 | Z 坐标 |
count | int | 1 | 粒子数量 |
dx | number | 0 | X 偏移/扩散范围 |
dy | number | 0 | Y 偏移/扩散范围 |
dz | number | 0 | Z 偏移/扩散范围 |
speed | number | 0 | 粒子速度 |
某些粒子需要此简易 API 不提供的额外数据,例如 DUST 和 FALLING_DUST。请使用 CLOUD、SMOKE、CAMPFIRE_COSY_SMOKE、SNOWFLAKE、FLAME、HAPPY_VILLAGER、HEART、NOTE、ENCHANT 等无数据粒子。彩色粉尘请使用世界表参考中的 world:spawn_dust(...)。
由于这个简单 API 不提供 Bukkit 粒子数据对象,需要数据的粒子可能会抛出底层的 Bukkit 异常并停用脚本实例。在 FMM/MagmaCore 脚本中请优先使用上面这些不需要数据的粒子名称。
world:play_sound(sound, x, y, z, volume, pitch)
在某位置播放声音。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
sound | string | 必填 | Bukkit Sound 枚举名(例如 "ENTITY_EXPERIENCE_ORB_PICKUP")或自定义资源包声音键(例如 "mypack:boss.roar") |
x | number | 必填 | X 坐标 |
y | number | 必填 | Y 坐标 |
z | number | 必填 | Z 坐标 |
volume | number | 1.0 | 音量 |
pitch | number | 1.0 | 音调 |
该名称会先与 Bukkit 已注册的音效进行匹配(不区分大小写,因此 "ENTITY_ARROW_SHOOT" 和 "entity_arrow_shoot" 是同一个)。如果没有匹配项,它会被规范化为小写的带命名空间键,并作为自定义资源包音效播放。既不是已知音效、也不是有效命名空间键的值不会播放任何东西,也不会抛出错误,因此拼写错误只是静默地什么都不做,而不会停用你的脚本。
同样的规则适用于 world:play_sound_at_location(loc, sound, volume, pitch)。
world:strike_lightning(x, y, z)
在某位置召唤闪电(视觉效果与伤害)。
| 参数 | 类型 | 说明 |
|---|---|---|
x | number | X 坐标 |
y | number | Y 坐标 |
z | number | Z 坐标 |
world:get_time()
返回当前世界时间(tick)。
world:set_time(ticks)
设置世界时间。
| 参数 | 类型 | 说明 |
|---|---|---|
ticks | int | 世界时间(0 = 黎明,6000 = 正午,13000 = 夜晚,18000 = 子夜) |
world:get_nearby_entities(x, y, z, radius)
返回以指定坐标为中心、轴对齐盒内的实体包装表,数组从 1 开始。不排除脚本所有者。
| 参数 | 类型 | 说明 |
|---|---|---|
x | number | 中心 X |
y | number | 中心 Y |
z | number | 中心 Z |
radius | number | 搜索半径(在三个坐标轴上都作为半边长) |
返回范围内所有实体,包括掉落物、投射物等非生物实体。调用生物方法前请检查 if entity.damage then。
world:get_nearby_players(x, y, z, radius)
返回轴对齐盒内的玩家表数组,从 1 开始,radius 是每个轴的半边长。不按游戏模式过滤。
| 参数 | 类型 | 说明 |
|---|---|---|
x | number | 中心 X |
y | number | 中心 Y |
z | number | 中心 Z |
radius | number | 搜索半径 |
world:spawn_entity(entity_type, x, y, z)
在给定位置生成一个原版 Minecraft 实体。
| 参数 | 类型 | 说明 |
|---|---|---|
entity_type | string | Bukkit 实体类型名称,小写(例如 "zombie"、"skeleton"、"pig") |
x | number | X 坐标 |
y | number | Y 坐标 |
z | number | Z 坐标 |
返回实体表,适用时含生物方法。未知枚举名返回 nil;有效但不可生成的类型可能抛出错误。此方法不检查区块是否已加载。
world:get_highest_block_y(x, z)
返回 Bukkit 查询到的 X/Z 最高方块 Y。与 get_block_at 不同,不检查区块加载状态。
| 参数 | 类型 | 说明 |
|---|---|---|
x | int | 方块 X 坐标 |
z | int | 方块 Z 坐标 |
world:raycast(from_x, from_y, from_z, dir_x, dir_y, dir_z, [max_distance], [fluid_mode], [blocks_only], [loaded_only])
从某点沿某方向投射射线,并返回命中信息。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
from_x | number | 必填 | 起点 X |
from_y | number | 必填 | 起点 Y |
from_z | number | 必填 | 起点 Z |
dir_x | number | 必填 | 方向 X 分量 |
dir_y | number | 必填 | 方向 Y 分量 |
dir_z | number | 必填 | 方向 Z 分量 |
max_distance | number | 50 | 最大射线距离 |
fluid_mode | string | "NEVER" | NEVER、SOURCE_ONLY 或 ALWAYS |
blocks_only | boolean | false | 忽略实体,只检测方块 |
loaded_only | boolean | false | 射线包围矩形内有未加载区块时,不追踪射线而返回 nil |
使用 loaded_only=true 时也要检查 nil 结果。否则,未命中会返回命中字段为 nil 的表:
| 字段 | 类型 | 说明 |
|---|---|---|
hit_entity | entity table or nil | 射线命中的第一个实体,若无则为 nil |
hit_location | location table or nil | 射线命中位置的精确坐标 |
hit_block | table or nil | 命中方块的 {x, y, z, material},若未命中方块则为 nil |
方块材质为小写。组合检测使用实体射线尺寸 0.5,没有实体过滤器。区块检查覆盖整个包围矩形,因此可能因偏离实际射线的未加载区块而拒绝。不合法的流体模式名会抛出错误。
world:spawn_firework(x, y, z, colors, [type], [power])
在给定位置生成烟花火箭。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
x | number | 必填 | X 坐标 |
y | number | 必填 | Y 坐标 |
z | number | 必填 | Z 坐标 |
colors | table | 必填 | 颜色字符串数组,例如 {"RED", "BLUE", "WHITE"} |
type | string | "BALL" | 烟花形状:"BALL"、"BALL_LARGE"、"STAR"、"BURST"、"CREEPER" |
power | int | 1 | 飞行强度,0-127 |
-- Example: red and yellow firework burst
context.world:spawn_firework(loc.x, loc.y, loc.z, {"RED", "YELLOW"}, "BURST", 2)
world:place_temporary_block(x, y, z, material, [ticks], [require_air])
放置临时方块,并在正数时长后恢复保存的 BlockData。不保存完整方块状态或物品栏。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
x | int | 必填 | 方块 X 坐标 |
y | int | 必填 | 方块 Y 坐标 |
z | int | 必填 | 方块 Z 坐标 |
material | string | 必填 | Bukkit Material 名称(例如 "stone"、"ice") |
ticks | int | 0 | 恢复前的 tick 数。零或负数会永久放置方块且不追踪 |
require_air | boolean | false | 若为 true,只有目标为空气时才放置方块 |
材质无效或目标区块未加载时返回 false,否则请求被接受后返回 true。require_air=true 且目标不是空气时,即使请求被接受,管理器也会跳过替换。
tick 为正数时,管理器会保存原方块数据,并在计时器结束、区块或世界卸载,或插件关闭时恢复。只有当该位置仍是这个临时方块时才会恢复,因此之后的编辑不会被覆盖。在仍为临时方块的位置再次放置,会重新开始计时,并保留第一次保存的原方块。带有方块实体的方块(例如箱子和告示牌),以及已被其他临时方块所有者(例如附魔租约)占用的方块会被跳过。破坏临时方块不会掉落任何东西,并会结束追踪且不恢复;在该处放置方块同样会结束追踪。
这些计时器不会随 Lua 实例取消,放置也不自动检查区域保护。
world:drop_item(x, y, z, material, [amount])
在给定位置以自然散落的方式掉落一个物品实体。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
x | number | 必填 | X 坐标 |
y | number | 必填 | Y 坐标 |
z | number | 必填 | Z 坐标 |
material | string | 必填 | Bukkit Material 名称 |
amount | int | 1 | 堆叠数量 |
返回所掉落物品的实体表,若材质无效则返回 nil。
context.zones
区域表创建空间区域并监视玩家进出。FMM 道具和 NPC 使用这里的 create_sphere(...)、create_cylinder(...)、create_cuboid(...)、watch(...)、unwatch(...),触发 on_zone_enter 和 on_zone_leave。区域属于实例,实例关闭时自动清理。Boss 使用独立专属表,含 get_entities_in_zone(...)、get_locations_in_zone(...)、watch_zone(...);参阅 EliteMobs 区域与目标。
zones:create_sphere(x, y, z, radius)
以指定坐标为中心创建球体,返回数字句柄;所有者没有世界时返回 nil。
| 参数 | 类型 | 说明 |
|---|---|---|
x | number | 中心 X |
y | number | 中心 Y |
z | number | 中心 Z |
radius | number | 球体半径 |
zones:create_cylinder(x, y, z, radius, height)
创建圆柱,返回数字句柄;所有者没有世界时返回 nil。
| 参数 | 类型 | 说明 |
|---|---|---|
x | number | 中心 X |
y | number | 中心 Y(底部) |
z | number | 中心 Z |
radius | number | 圆柱半径 |
height | number | 圆柱高度 |
zones:create_cuboid(x, y, z, xSize, ySize, zSize)
创建长方体并返回数字句柄;所有者没有世界时返回 nil。通用监视检测玩家的位置点,因此尺寸为相对中心的半边长,与 Boss 区域的实体重叠和采样点操作不同。若 zSize 为零而 xSize 非零,共享形状会用 xSize 替代 Z。
| 参数 | 类型 | 说明 |
|---|---|---|
x | number | 中心 X |
y | number | 中心 Y |
z | number | 中心 Z |
xSize | number | X 方向上的半边长 |
ySize | number | Y 方向上的半边长 |
zSize | number | Z 方向上的半边长 |
zones:watch(handle, onEnterCallback, onLeaveCallback)
启用已创建区域的进出通知。回调参数必须是 Lua 函数,但不会直接执行函数体,逻辑通过 on_zone_enter 和 on_zone_leave 运行。成员追踪从创建时开始,因此稍后添加 watch 不会为已经记录在内部的玩家补发进入事件。后续调用传入 nil 不会清除已启用的通知。
| 参数 | 类型 | 说明 |
|---|---|---|
handle | int | 来自 create_* 调用的区域句柄 |
onEnterCallback | function 或 nil | 函数启用 on_zone_enter,函数体被忽略 |
onLeaveCallback | function 或 nil | 函数启用 on_zone_leave,函数体被忽略 |
若监视设置成功返回 true,若区域句柄无效则返回 nil。
检查在 on_game_tick 前执行,使用所有者当前世界中玩家的位置点,而不是整个玩家包围盒。球体要求严格位于半径内;圆柱要求在半径内,并包含底面、不包含顶面。区域保留创建时的坐标和世界。玩家断线或移除监视不保证触发离开钩子。请保持合理的区域数量。
zones:unwatch(handle)
停止监视某区域并清理其资源。
| 参数 | 类型 | 说明 |
|---|---|---|
handle | int | 要停止监视的区域句柄 |
示例:邻近触发区域
return {
api_version = 1,
on_spawn = function(context)
local loc = context.prop.current_location
if loc == nil then return end
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, 5)
-- Pass placeholder functions to enable on_zone_enter and on_zone_leave hooks.
-- The placeholder bodies are ignored; the actual logic goes in the hooks below.
context.zones:watch(handle, function() end, function() end)
context.state.zone_handle = handle
end,
on_zone_enter = function(context)
context.log:info("Player entered zone!")
end,
on_zone_leave = function(context)
context.log:info("Player left zone!")
end,
on_destroy = function(context)
if context.state.zone_handle then
context.zones:unwatch(context.state.zone_handle)
end
end
}
context.event
钩子有事件或执行者时存在该表。FMM 道具点击使用插件取消标志;NPC 交互和通用区域钩子可以提供执行者而没有 Bukkit 事件。在 on_game_tick、调度回调和没有触发玩家的生命周期钩子中不存在。
FMM 道具的 on_projectile_hit 钩子会收到可取消的 FreeMinecraftModels 弹射物命中事件,因此提供 cancel() 和 uncancel()。该钩子在 FMM 处理完命中之后才运行,所以取消并不能阻止非穿透弹射物被消耗。如果其他插件已经取消了这次命中,该钩子不会触发。当射击者是生物实体时,context.event.player 就是射击者(可能是怪物),否则不存在;只有当玩家发射弹射物时才会设置 context.player。该钩子还以实体表形式提供弹射物 context.projectile。
通用 on_zone_enter、on_zone_leave 中的 context.player 和 context.event.player 指进入或离开的玩家。它们没有可取消的 Bukkit 事件,因此不提供 cancel()、uncancel()。
| 字段 / 方法 | 类型 | 说明 |
|---|---|---|
is_cancelled | boolean | 首次解析事件子表时的取消状态。调用 cancel() 或 uncancel() 不会刷新它 |
cancel() | method | 取消事件;只在可取消事件中存在 |
uncancel() | method | 撤销取消;只在可取消事件中存在 |
player | entity table | 参与该事件的玩家(如有) |
on_right_click = function(context)
if context.event and context.event.cancel then
context.event:cancel() -- prevent the default right-click interaction
end
end
EliteMobs 能力脚本提供更详细的事件表,包含伤害、原因、来源及修改方法。完整字段请参阅 Lua API 参考。
em 辅助命名空间
em 在文件加载时可用。构造器只创建数据,不加载世界、注册监视或验证完整形状。返回的 setter 修改其自身表,并保留传入位置表的引用。
| 函数 | 用途 |
|---|---|
em.create_location(x, y, z [, world, yaw, pitch]) | 创建带有可选世界名、yaw 和 pitch 的位置表。返回的表还有一个 .add(dx, dy, dz) 方法,可就地偏移位置并返回自身以便链式调用。 |
em.create_vector(x, y, z) | 创建向量表 |
em.zone.create_sphere_zone(radius) | 创建球形区域定义 |
em.zone.create_dome_zone(radius) | 创建圆顶区域定义 |
em.zone.create_cylinder_zone(radius, height) | 创建圆柱形区域定义 |
em.zone.create_cuboid_zone(x, y, z) | 创建长方体区域定义 |
em.zone.create_cone_zone(length, radius) | 创建锥形区域定义 |
em.zone.create_static_ray_zone(length, thickness) | 创建静态射线区域定义 |
em.zone.create_rotating_ray_zone(length, point_radius, animation_duration) | 创建旋转射线区域定义 |
em.zone.create_translating_ray_zone(length, point_radius, animation_duration) | 创建平移射线区域定义 |
em.location.is_in_dungeon(loc) | 检查位置是否位于任意已注册地下城区域内(任何报告了 "dungeon" 类型的拥有者) |
em.location.is_protected(loc) | 检查位置是否位于任意受保护区域内(WorldGuard、GriefPrevention、EM 区域等) |
em.location.owned_by(loc, namespace) | 检查指定的插件命名空间是否拥有该位置(例如 "EliteMobs") |
em.location.owners(loc) | 拥有该位置的命名空间字符串数组 |
em.location.kinds_at(loc) | 该位置的类型标签数组("elitemobs"、"dungeon"、"world_package"、"open_world_dungeon"、地下城规模分类) |
em.location.has_kind(loc, kind) | 检查位置是否被打上指定类型标签 |
MagmaCore 自带 WorldGuard 和 GriefPrevention 适配器,使用方在启用时初始化,保护查询也可按需初始化。查询覆盖已注册提供方,不是所有保护插件。已注册区域适配器失败时报告为受保护。没有提供方或保护归属时返回 false;警告会指出缺少的提供方。
em.location.* 要求 world 为已加载世界名或 {name=...},不会回退到所有者世界。世界缺失、无法解析或输入为 nil 时返回 false 或空数组;缺失坐标默认为零,但格式错误的数字字段可能抛出错误。所有权命名空间区分大小写,类型标签不区分。拥有某位置不自动代表它属于地下城或受到保护。数组顺序没有保证。
区域构造器返回可链式调用的表,提供 :set_center(loc)(或根据区域类型提供 :set_origin(loc) / :set_destination(loc)):
-- At file scope: create a reusable zone shape
local blast_zone = em.zone.create_sphere_zone(5)
return {
api_version = 1,
on_spawn = function(context)
-- Anchor the zone at call time
blast_zone:set_center(context.boss:get_location())
local entities = context.zones:get_entities_in_zone(blast_zone)
-- ...
end
}
em 命名空间在 EliteMobs 中尤其有用,区域定义会与 context.zones:get_entities_in_zone() 以及 context.script 一起使用。在 FreeMinecraftModels 中,更常用的是 context.zones:create_sphere(...) / create_cylinder(...) / create_cuboid(...) 方法来创建基于监视的简单区域。
实体表
下表概述常用方法。完整 Lua API 参考还包含投射物字段、移动路径检查、所有权及保护辅助方法。它们适用于通用实体表;Boss 能力的专属包装表另有章节。
世界查询和事件数据返回实体表,根据实体类型提供不同层次的字段和方法。
实体基础字段
| 字段 | 类型 | 说明 |
|---|---|---|
uuid | string | 实体的 UUID |
entity_type | string | 实体类型(例如 "player"、"zombie"、"skeleton"、"villager") |
is_valid | boolean | 实体引用是否仍然有效 |
is_dead | boolean | 实体是否已死亡 |
is_player | boolean | 实体是否为玩家 |
is_hostile | boolean | 是否实现 Bukkit Monster,并非当前是否具有敌意 |
is_passive | boolean | 是否实现 Bukkit Animals,不保证被动行为 |
current_location | location table | 实体当前位置(x、y、z、world、yaw、pitch) |
world | string | 实体所在的世界名称 |
实体基础方法
| 方法 | 返回值 | 说明 |
|---|---|---|
teleport(location_table) | void | 必须提供 x、y、z;世界省略或无法解析时使用实体世界。yaw、pitch 默认零。不自动检查移动路径或保护 |
remove() | void | 将实体从世界中移除。仅在实体仍然有效时生效。 |
set_silent(flag) | void | 抑制或重新启用实体的声音。 |
set_invulnerable(flag) | void | 使实体免疫或恢复受到伤害。 |
set_gravity(flag) | void | 启用或禁用实体的重力。 |
set_glowing(flag) | void | 切换实体的发光描边效果。 |
生物实体字段
生物实体(玩家、生物等)拥有所有实体基础字段,并额外包含:
| 字段 | 类型 | 说明 |
|---|---|---|
health | number | 当前生命值 |
maximum_health | number | 最大生命值 |
name | string | 显示名称 |
is_alive | boolean | 实体是否存活 |
生物实体方法
| 方法 | 返回值 | 说明 |
|---|---|---|
damage(amount) | void | 对实体造成指定数值的伤害 |
push(x, y, z) | void | 立即将向量加到当前速度上 |
set_facing(x, y, z) | void | 在原地以新朝向传送 |
add_potion_effect(effect, duration, amplifier) | void | 添加药水效果。effect 为字符串(例如 "speed"、"slowness"、"regeneration")。duration 单位为 tick。amplifier 为效果等级减 1(0 = 等级 I)。 |
remove_potion_effect(effect) | void | 按名称移除药水效果 |
get_scale() | number | 返回包含修饰符的有效缩放值,不可用时返回 1.0 |
set_scale(value) | void | 属性可用时修改其基础缩放值 |
并非所有由 get_nearby_entities() 返回的实体都是生物实体。你可以使用 entity.is_player、entity.is_hostile 或 entity.is_passive 按类别过滤,或者在调用 damage()、push()、add_potion_effect() 等生物实体方法前先用 if entity.damage then 进行检查。
插件集成字段
实体表可包含插件集成字段。没有对应增强器时字段不存在,读取为 nil。跟踪标志、模型身份、管理员状态和大部分嵌套数据是构建时快照,不随之后的跟踪变化更新。共享 EliteMobs 生命值字段按需读取;模型及权限方法在调用时查询底层对象。
EliteMobs 字段
在已安装 EliteMobs 时可用。
| 字段 | 类型 | 说明 |
|---|---|---|
is_elite | boolean | 实体是否为 EliteMobs 精英 |
is_custom_boss | boolean | 实体是否为 EliteMobs 自定义 Boss(在 elite 子表中也可用) |
is_significant_boss | boolean | 实体是否为生命值倍率大于 1 的自定义 Boss(即设计过的遭遇战,而非填充用的精英) |
elite | table or nil | 精英信息子表(见下文)。若实体不是精英则为 nil。 |
elite 子表包含:
| 字段 | 类型 | 说明 |
|---|---|---|
elite.level | int | 精英的等级 |
elite.name | string 或 nil | 精英的显示名称 |
elite.health | number | 当前生命值 |
elite.max_health | number | 最大生命值 |
elite.is_custom_boss | boolean | 是否为自定义 Boss(而非自然出现的精英) |
elite.health_multiplier | number | 配置文件定义的生命值倍率 |
elite.damage_multiplier | number | 配置文件定义的伤害倍率 |
elite:remove() | void | 通过 EliteMobs 移除流程删除精英,不等于击杀,也不保证掉落 |
示例:对精英造成不同伤害
local entities = context.world:get_nearby_entities(x, y, z, 5)
for _, entity in ipairs(entities) do
if entity.is_elite then
-- Deal double damage to elites
entity:damage(20)
context.log:info("Hit elite: " .. entity.elite.name .. " (level " .. entity.elite.level .. ")")
elseif entity.is_hostile then
entity:damage(10)
end
end
FreeMinecraftModels 字段
在已安装 FreeMinecraftModels 时可用。
| 字段 | 类型 | 说明 |
|---|---|---|
is_modeled | boolean | 实体是否附加了 FMM 模型(DynamicEntity 或 PropEntity) |
is_prop | boolean | 实体是否为 FMM 道具(静态装饰实体) |
model | table or nil | 模型信息子表(见下文)。若实体未模型化则为 nil。 |
model 子表包含:
| 字段 | 类型 | 说明 |
|---|---|---|
model.model_id | string 或 nil | 模型蓝图 ID(例如 "torch_01") |
model.is_dynamic | boolean | 模型化实体是否为 DynamicEntity(生物/Boss 模型),否则为静态道具 |
model:play_animation(name, [blend], [loop]) | boolean | 接受动画请求。blend 和 loop 默认 false。排队或被抑制的攻击也可能返回 true,不代表立即播放。终结死亡状态会拒绝播放 |
model:stop_animations() | void | 清除播放队列,并在可用时回到待机动画;不改变终结死亡状态 |
model:remove() | void | 通过 FMM 的正确清理流程移除模型化实体。 |
在玩家实体表上,FreeMinecraftModels 增强器还会添加:
| 字段 / 方法 | 类型 | 说明 |
|---|---|---|
is_op | boolean | 该玩家是否为服务器 OP。 |
has_permission(node) | boolean | 该玩家是否拥有该权限节点。 |
这两个玩家值只存在于 FreeMinecraftModels 脚本中。在 EliteMobs 内部构建的共享表上,EliteMobs 自己的 FreeMinecraftModels 适配器会添加 is_modeled、is_prop 和 model,但不会添加 is_op 或 has_permission。
FMM 玩家表还提供 is_holding_elitemobs_item(filename),检查主手 EliteMobs 文件名标签(包括 .yml),而不是显示名。空手、其他物品或集成不可用返回 false。EliteMobs 自己的共享增强器在数据可用时添加包含战斗 tier 的 elite_player;FMM 的可选 EliteMobs 适配器不添加该子表。
模型桥接的 blend、loop 默认为 false。道具表 的 play_animation 两者默认 true,启用排队和循环。blend 控制排队,不是动画交叉淡化。
示例:按类型过滤实体
local entities = context.world:get_nearby_entities(x, y, z, 10)
for _, entity in ipairs(entities) do
if entity.is_prop then
-- Skip props
elseif entity.is_player then
entity:damage(5)
elseif entity.entity_type == "villager" then
-- Don't hurt villagers
elseif entity.is_elite then
entity:damage(20)
elseif entity.is_hostile then
entity:damage(10)
end
end
玩家专属字段
玩家实体拥有所有实体和生物实体字段,并额外包含:
| 字段 | 类型 | 说明 |
|---|---|---|
game_mode | string | 玩家的游戏模式("creative"、"survival"、"adventure"、"spectator") |
玩家专属方法
只要 API 中写着「支持颜色代码」,Magmacore 就会把该字符串交给内置的 MiniMessage 解析器处理。传统的 & 代码、&#RRGGBB 十六进制、<#RRGGBB>、<gradient>、<rainbow> 以及 MiniMessage 其余的标签集都可用,并且可以自由混用。Kyori Adventure 不是依赖项,脚本也无法使用任何 Adventure API —— 你并不需要它。
Lua 文本方法转为传统颜色代码字符串,颜色、渐变和装饰可渲染;交互标签(<hover>、<click>、<insert>、<font>、<key>、<lang>)虽被解析但不生效。常见格式见颜色代码。
| 方法 | 返回值 | 说明 |
|---|---|---|
send_message(msg) | void | 向玩家发送聊天消息。支持颜色代码。 |
chat(text) | void | 将经过颜色处理的文本交给 Bukkit 玩家聊天;带斜杠的命令以该玩家身份执行 |
get_held_item() | table or nil | 返回玩家主手物品的 {type, amount, display_name},若手为空则为 nil。type 是大写的 Bukkit Material 名称,例如 "GOLD_INGOT"。 |
consume_held_item(amount?) | void | 消耗请求入队,针对执行时的主手物品。默认 1;不保留检查过的物品,也不确认付款。请传正数,负数不会被拒绝 |
has_item(material, amount?) | boolean | 若玩家在背包中拥有至少 amount(默认 1)数量的指定材质,返回 true |
get_target_entity([range]) | entity table 或 nil | 从眼部检测方块和实体,排除玩家自身,实体射线尺寸 0.5。忽略流体及可通行方块,默认范围 50 |
get_eye_location() | location table | 返回玩家眼睛高度处的位置表 |
get_look_direction() | table | 返回玩家注视方向的 {x, y, z} 方向向量 |
send_block_change(x, y, z, material, [ticks]) | boolean | 客户端方块修改入队,正数 tick 后复原。true 表示入队;无效或非方块材质返回 false |
reset_block(x, y, z) | boolean | 将真实方块的显示恢复入队,并返回 true |
sleep(x, y, z) | void | 创建真实白床及尝试睡眠的请求入队。醒来或断线时只恢复 BlockData,不恢复方块实体内容 |
wake_up() | void | 唤醒正在睡眠的玩家。 |
排队的方块修改和恢复在执行时读取玩家世界,不要依赖它们在换世界后恢复原先的方块。睡眠清理在 20 tick 后检查,之后每 10 tick 检查。这些任务不归 context.scheduler 所有。
玩家 UI 方法
这些是共享玩家表方法。Boss 能力的玩家表使用不同 UI 签名,见 API 参考。共享条按宿主插件的 MagmaCore 副本内的玩家标识,不按脚本区分。到期计时器只会移除启动它的那个 Boss 条或重复动作栏,因此旧的到期不会清除新的条目。其 Bukkit 计时器不属于 context.scheduler。
player:show_boss_bar(text, [color], progress, [ticks])
替换共享的实心 Boss 条。进度限制在 0..1,无效颜色变为白色。省略颜色时传 nil,确保进度仍是第三个参数。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
text | string | 必填 | 要显示的文字。支持 & 颜色代码。 |
color | string | "WHITE" | 血条颜色。可选:"RED"、"BLUE"、"GREEN"、"YELLOW"、"PURPLE"、"PINK"、"WHITE" |
progress | number | 必填 | 填充比例,0.0(空)到 1.0(满) |
ticks | int | -1 | 正数安排移除;否则保持到隐藏或替换 |
player:hide_boss_bar()
从玩家屏幕上移除 Boss 血条。无参数。
player:show_action_bar(text, [ticks])
在动作栏(快捷栏上方)显示文字。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
text | string | 必填 | 要显示的文字。支持 & 颜色代码。 |
ticks | int | -1 | 正数每 40 tick 重发直到到期;否则只发一次。新调用取消旧重复任务 |
player:show_title(title, [subtitle], fade_in, stay, fade_out)
向玩家显示标题界面。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
title | string | 必填 | 主标题文字。支持 & 颜色代码。 |
subtitle | string | "" | 主标题下方的副标题文字。可选。 |
fade_in | int | 必填 | 淡入时长(tick) |
stay | int | 必填 | 标题在屏幕上停留的时长(tick) |
fade_out | int | 必填 | 淡出时长(tick) |
下一步
获取插件专属的钩子、API 和工作流: