Lua 脚本:入门指南
你将学到什么
本页将教你编写 EliteMobs 的第一个 Lua 能力,从空白文件一直到可用的 Boss 技能。结束时你将理解钩子、上下文、冷却时间以及每个 Lua 能力文件的一般结构。
熟悉基础知识后,继续阅读以下配套页面:
- 钩子与生命周期 -- 钩子名称、触发顺序和上下文可用性
- NPC 脚本 -- NPC 生命周期、交互和邻近钩子
- Boss 与实体 --
context.boss、context.player、context.players和context.entitiesAPI - 世界与环境 -- 粒子、声音、闪电、生成和
context.worldAPI - 区域与目标 -- 原生 Lua 区域、脚本工具目标和
context.zones/context.scriptAPI - 示例与模式 -- 可学习和改编的完整工作能力
- 枚举与值 -- Particle、Material、PotionEffectType 等字符串常量的 Spigot Javadoc 链接
- 故障排除 -- 常见错误、调试技巧和 QC 清单
- 行为程序 -- 用 Lua 编写、通过 Boss 的
behavior设置选用的怪物 AI
EliteMobs 的大多数内置能力使用 Lua,但辅助方法的名称和行为可能随版本变化。自定义能力投入生产前应在预发布服务器上检查,更新后也应重新查阅参考文档。
Lua 不会替代现有的 eliteScript: YAML 系统,而且 EliteScript 也没有被弃用。
- 当你需要使用现有的动作、目标、区域、条件、冷却时间和相对向量页面进行声明式 YAML 驱动脚本时,请使用 EliteScript。
- 当你需要变量、循环、随机选择、可复用辅助函数、每个 Boss 的持久状态和更传统的编程流程时,请使用 Lua 能力文件。
脚本工具 API(context.script)把规格表传给 EliteScript 的目标、区域、相对向量和粒子类。它们的解析器和限制仍然适用,但独立的 Lua 句柄并不具备 YAML 动作的所有上下文。继承目标和落地位置需要这些句柄缺少的上下文;Lua 辅助方法也不会自动应用 YAML 动作的等待、条件或目标缓存。区域与目标页面解释了这些差异。
什么是 Lua 能力
Lua 能力是独立的 .lua 文件,位于 EliteMobs 正常的 powers 目录树中,引用方式与普通能力文件完全相同。
本页讲的是 Boss Lua 能力。NPC Lua 脚本是另一项独立功能:它们位于 plugins/EliteMobs/npc_scripts/,通过 NPC 配置中的 scripts: 列表挂接,并使用诸如 on_npc_proximity_enter 的 NPC 专属钩子。参见 NPC 脚本。
Lua 能力的优势
Lua 能力在以下情况下表现出色:
- 攻击轮换和随机技能选择
- 使用
context.state在钩子间保持持久状态 - 无需使用 YAML 等待构建一切的延迟和重复动作
- 在单个文件内共享自定义辅助函数
- 纯 EliteScript 中难以处理的复杂分支
- 仍然想复用 EliteScript 风格目标和区域定义的 Boss 逻辑
如果你的能力主要是"触发事件,执行几个脚本动作",现有的 EliteScript 文档仍然是最快最清晰的构建方式。如果你的能力需要真正的程序流程,Lua 就是合适的工具。
本页面面向的读者
本页面为三类读者编写:
- 已经了解 EliteScript,想学习 Lua 但不想一次学会"真正的编程"的人
- EliteMobs 脚本新手,需要包含精确名称的完整参考的人
- 使用 AI 起草能力,需要足够的细节来判断 AI 何时编造了虚假信息的人
在编写有用的能力之前,你不需要成为完整的 Lua 开发者。对于大多数实用的 EliteMobs 能力,你真正需要的只是:
- 如何在返回的表中放置有效的钩子
- 如何从
context读取值 - 如何使用
if ... then return end提前退出 - 如何正确调用几个辅助方法
- 如何从现有 EliteScript 文档中复制正确的目标和区域规范
心智模型:EliteScript vs. Lua
如果你了解 EliteScript,这个对比是理解 Lua 能力的最快方式:
| 如果你用 EliteScript 术语思考 | 在 Lua 中通常意味着 |
|---|---|
| Events | 钩子名称如 on_spawn 或 on_boss_damaged_by_player |
| Cooldowns | context.cooldowns(见下方 context.cooldowns) |
| Actions | 直接方法调用如 context.world:spawn_particle_at_location(...) 或 context.script:damage(...) |
| Targets | context.script:target({...}) -- 使用 EliteScript Target 字段名 |
| Zones | context.script:zone({...}) 或原生 context.zones 辅助 -- 见区域与目标 |
| Relative vectors | context.script:relative_vector({...}) 或 context.vectors |
| Script flow | 你自己的 Lua if 语句、辅助函数、定时器和状态 |
最大的区别是:
- EliteScript 在 YAML 中描述应该发生什么。
- Lua 让你用代码决定何时、为什么以及哪个分支应该执行。
如果 EliteScript 感觉像"配置",那么 Lua 感觉像"配置加决策"。
Lua 极简入门
多数能力使用几个 Lua 概念:变量(local x = 5)、函数(function foo() end)、if 检查(if x then ... end)、表({key = value})和 nil(没有值)。分号可选。表使用花括号,函数和控制流程块使用 end 结束。
关于带示例的完整讲解,请参阅 MagmaCore Lua 脚本引擎 —— Lua 极简入门。该入门内容在所有 Nightbreak 插件之间共享,学一次即可处处适用。
你的第一个工作能力,逐步构建
如果你是完全的新手,这是通往"第一次成功"的最佳路径。
步骤 1 之前:保存并附加到 Boss
将 Lua 文件保存在 EliteMobs 的正常能力文件夹中,例如:
plugins/
EliteMobs/
powers/
first_test.lua
然后使用正常的 powers: 列表将该文件名添加到 Boss 配置中:
powers:
- first_test.lua
完整的新手流程是:
- 将文件保存到
plugins/EliteMobs/powers/ - 将
.lua文件名添加到 Boss 的powers:列表 - 执行
/em reload加载修改的文件,再生成一个新的测试 Boss。重载影响插件及其活动内容,因此请使用预发布服务器。 - 触发钩子,检查可见结果和控制台。只保存文件,或不重载就生成另一个 Boss,都不会更新已加载的定义。
如果需要更多关于 Boss 文件、能力列表或自定义 Boss 结构的背景,请参阅创建自定义 Boss。那个页面详细介绍了 Boss 的搭建,包括 powers: 列表是如何工作的。
步骤 1:让文件加载
return {
api_version = 1,
on_spawn = function(context)
end
}
加载器会验证 Lua 语法、返回表和钩子名称。空钩子没有可见结果,因此仅仅没有报错,不能证明文件已被发现或附加。添加步骤 2 的声音并生成一个新 Boss,验证整个过程。
步骤 2:让 Boss 做一件可见的事
return {
api_version = 1,
on_spawn = function(context)
context.boss:play_sound_at_self("entity.blaze.ambient", 1.0, 1.0)
end
}
现在你有了最重要的新手证明:你的钩子正在触发。
步骤 3:响应玩家的攻击
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end
context.player:send_message("&eYou hit the boss.")
end
}
这教授三个核心概念:
on_boss_damaged_by_player是钩子名称context.player是参与该钩子的玩家return在缺少所需数据时提前退出
步骤 4:使用冷却时间防止刷屏
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end
if not context.cooldowns:local_ready("hello_message") then
return
end
context.player:send_message("&eYou woke up the boss.")
context.cooldowns:set_local(60, "hello_message")
end
}
本地冷却的每刻表示实际经过的 50 毫秒:即使服务器 TPS 下降,60 也始终是三秒。这个命名冷却在同一 Boss 的能力之间共享,不在不同 Boss 之间共享。组合自定义能力时,请给键加上能力名称前缀。本例只检查自身的本地键,不检查 Boss 的全局冷却。
步骤 5:添加一个真实效果
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end
if not context.cooldowns:local_ready("shock") then
return
end
context.cooldowns:set_local(100, "shock")
context.player:send_message("&cStatic jumps from the boss into your armor!")
context.world:strike_lightning_at_location(context.player.current_location)
end
}
这会在世界中产生真实闪电,不是纯视觉闪光。先设置冷却再执行效果,使效果触发的事件也能看到该冷却。这个 100 刻的本地冷却表示实际经过的五秒。
第一个真实工作流程
从头构建新的 Lua 能力时,使用以下顺序:
- 创建文件并让
on_spawn工作。 - 切换到你实际需要的钩子。
- 确认钩子具有你期望的数据,如
context.player或context.event。 - 先添加消息或声音,在伤害或粒子之前。
- 添加冷却时间。
- 添加一个游戏效果。
- 之后才添加辅助函数、状态、调度器逻辑或区域。
这个顺序使调试大大简化,因为每次只改变一件事。
Lua 文件的位置
将 .lua 文件放在与普通能力 .yml 文件相同的文件夹树中:
plugins/
EliteMobs/
powers/
mycoolpower.lua
attack_push.yml
subfolder/
myotherpower.lua
Lua 能力会从 EliteMobs 已经加载的能力目录中自动发现。
可选 YAML 元数据
自定义 mycoolpower.lua 可以在同一目录旁边配一个已加载的 mycoolpower.yml 或 mycoolpower.yaml。发现过程会读取该 YAML 的 isEnabled、effect、powerType、powerCooldown 和 globalCooldown;isEnabled: false 会阻止该自定义 Lua 文件注册。没有匹配的元数据时,不使用效果,能力类型为 MISCELLANEOUS。Lua 钩子仍自行控制攻击时机,并且必须自行检查冷却。
放在内置 Lua 能力旁边、已加载的同名 YAML 文件会对该能力应用相同的字段。没有这样的文件时,内置能力保留其随插件提供的元数据。各字段见创建能力。
Boss 如何引用 Lua 能力
Boss 文件仍然使用普通的 powers: 列表:
powers:
- attack_push.yml
- mycoolpower.lua
不需要特殊字段。Lua 能力不通过 eliteScript: 加载。
文件命名规则
- Boss 通过文件名引用 Lua 能力,而非文件夹路径。
- EliteMobs 目前仅通过基本名称注册发现的 Lua 能力。
- 避免重复的名称如
powers/fire.lua和powers/bosses/fire.lua。EliteMobs 只会加载完整路径按字母顺序排在前面的那个文件,并对被跳过的副本记录警告,除非两个文件完全相同。
预制 Lua 能力
大多数内置能力使用 Lua。当前源码定义了 68 个具体的预制 Lua 能力,在文件缺失时安装到 plugins/EliteMobs/powers/,包括 attack_fire.lua、frost_cone.lua、meteor_shower.lua 和 shield_wall.lua。可以阅读这些文件学习,但编辑过的本地副本可能不同于插件自带版本。
预制文件只在缺失时写入,因此修改在重启后仍保留。当某次更新修改了内置能力时,启动时会将未编辑过的旧版随附副本移至 plugins/MagmaCore/outdated files/EliteMobs/,并在原处写入当前版本;编辑过的副本会保留。删除一个内置文件后,下次插件初始化会重新生成自带版本;替换前请保留仍然需要的自定义修改。
共享源码片段
Boss Lua 能力可以用独占一行的注释指令包含辅助文件:
-- @include helpers/common.inc
加载器在验证能力前插入引用文件的文本。路径相对于包含指令的文件,并且必须保持在最外层能力文件的目录内,解析符号链接后也不例外。循环包含、超过 16 层的嵌套、大于 4 MiB 的文件,以及超过 4,194,304 字符的展开源码都会被拒绝。
辅助片段请使用 .inc 等非 .lua 扩展名,避免发现过程也将其当作独立能力加载。在能力最终的 return 表之前声明辅助函数。该指令仅适用于 Boss 能力加载,不会启用 Lua require 或文件系统访问,NPC 脚本也不会处理它。
旧的 .yml 名称仍然可以解析
引用 attack_fire.yml 的 Boss 配置仍然有效。对于 .yml 或 .yaml 引用,EliteMobs 先查找相同基本名称的已加载 Lua 能力,再回退到原名称。验证失败或被禁用的文件不是已加载的替代能力。请保持基本名称的拼写和大小写一致。要修改内置 Lua 能力,请编辑其现有文件并重载;在其他目录建立重复名称不是可靠的覆盖方式。
最小文件约定
每个 Lua 能力必须用 return 返回一个表。该表有意设计得很严格。
必需和可选的顶级字段
| 字段 | 必需 | 类型 | 备注 |
|---|---|---|---|
api_version | 是 | Number | 使用整数 1 |
priority | 否 | Number | 使用整数。较小的值先执行,默认为 0。YAML 能力的优先级为 0 |
| 支持的钩子键 | 否 | Function | 必须使用钩子参考中列出的确切钩子名称之一 |
验证规则
- 文件必须返回一个表。
api_version是必需的,当前必须为1。priority如果存在必须是数字。- 每个额外的顶级键必须是支持的钩子名称。
- 每个钩子键必须指向一个函数。
- 未知的顶级键会被拒绝。
这意味着辅助函数和局部常量应该放在最终 return 的上方,而非放在返回的表内部,除非它们是实际的钩子。
文件主体在验证时执行一次,并在每个 Boss 能力运行实例的独立 Lua 环境中再次执行。context 只作为钩子或回调参数存在,不是顶级全局变量。局部变量可以在该运行实例中保留,但验证时的值不会传入它。运行环境移除了文件、操作系统、Java 访问和 require;源码片段请使用包含指令。未捕获的钩子或回调错误会关闭该运行实例并取消其任务,下次受到攻击时不会重试。修正文件并重载后再试。
复制粘贴入门模板
最小的有效 Lua 能力
return {
api_version = 1,
on_spawn = function(context)
end
}
推荐入门模板
local ATTACK_COOLDOWN = "my_attack"
local function can_run_attack(context)
return context.cooldowns:local_ready(ATTACK_COOLDOWN)
and context.cooldowns:global_ready()
end
local function run_attack(context)
context.boss:play_sound_at_self("entity.blaze.shoot", 1.0, 1.0)
context.cooldowns:set_local(100, ATTACK_COOLDOWN)
context.cooldowns:set_global(20)
end
return {
api_version = 1,
priority = 0,
on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end
if not can_run_attack(context) then
return
end
run_attack(context)
end
}
大型文件布局
local CONSTANT_NAME = "value"
local function helper_function(context)
end
local function another_helper(context, value)
end
return {
api_version = 1,
priority = 0,
on_spawn = function(context)
end,
on_enter_combat = function(context)
end,
on_boss_damaged_by_player = function(context)
end,
on_exit_combat = function(context)
end
}
钩子
钩子是你返回的表中具有特殊名称的函数。EliteMobs 在发生某些事情时调用它们 -- Boss 生成、受到伤害、进入战斗等。你已经在上面的教程中看到了 on_spawn 和 on_boss_damaged_by_player。
最常用的入门钩子是 on_spawn、on_boss_damaged_by_player、on_enter_combat 和 on_exit_combat。所有钩子的完整列表、每个钩子中可用的上下文键以及执行顺序的工作方式,请参阅钩子与生命周期。
什么是 context?
每个钩子函数都接收一个名为 context 的参数。把它想象成 EliteMobs 每次发生事情时递给你的工具箱 -- 它包含了你与 Boss、玩家、世界、冷却时间等交互所需的一切。
on_boss_damaged_by_player = function(context)
-- context.boss = the boss that was hit
-- context.player = the player who hit it
-- context.world = tools for spawning particles, sounds, etc.
-- context.cooldowns = cooldown management
-- context.state = your own persistent storage
end
你不需要自己创建 context -- EliteMobs 创建它并传递给你的钩子。你只需从中读取并调用方法。
每个钩子接收新的 context。其中的 context.state 表属于此 Boss 上此能力的运行实例,可为之后的钩子和回调保存数据。其他能力有独立状态;关闭或重载运行实例会丢弃状态。
关键 context API
以下是你将从 context 使用的最重要的 API:
-
context.state-- 为此 Boss 上此能力的运行实例保留的普通 Lua 表。用它在钩子间保存阶段编号、任务 ID 和标志。运行实例移除或重载后不保留。 -
context.log-- 使用log:info(msg)、log:warn(msg)和log:debug(msg)记录控制台日志。需要区分多个能力的输出时,请在消息里写上能力名称。 -
context.cooldowns-- Boss 的各个能力共享命名冷却和全局冷却。显式键共享 Boss 范围内的命名空间,因此请加上能力名称前缀。nil键选择当前 Lua 文件名的默认冷却。cooldowns:check_local(key, ticks)在一次调用中检查并设置本地冷却,不检查也不设置全局冷却。完整 API 见钩子与生命周期。 -
context.scheduler--scheduler:run_after(ticks, callback)延迟一次回调。scheduler:run_every(ticks, callback)重复执行,首次不等待一个间隔;需要明确的首次延迟时使用scheduler:run_repeating(delay, interval, callback)。回调接收不含触发事件或玩家的新上下文。使用这个回调参数,并主动保存所需玩家 UUID 或位置;不要假定延迟后仍有ctx.player。仅限战斗的重复任务应在on_exit_combat中取消,否则所属任务会一直运行,直到取消或运行实例关闭。见钩子与生命周期。 -
context.boss/context.player-- 参与当前事件的 Boss 和玩家。所有字段和方法请参阅 Boss 与实体。 -
context.world-- 生成粒子、实体、声音、闪电、方块。请参阅世界与环境。 -
context.zones/context.script-- 区域几何、目标、伤害、粒子。请参阅区域与目标。
方法语法:: 与 .
关于 : 与 .,请参阅 MagmaCore Lua 脚本引擎。实体方法和共享服务接受两种形式,但 context.event.set_damage_amount(value) 与 context.event.multiply_damage_amount(multiplier) 必须使用点号;冒号会把事件表作为第一个参数传入。context.event:transfer_damage(target, amount) 接受冒号。em.create_location(...) 等全局构造函数使用点号。
后续步骤
现在你已经了解了基础知识,探索 Lua 脚本文档的其余部分:
- 钩子与生命周期 -- 钩子名称、执行顺序以及每个钩子中可用的上下文键
- NPC 脚本 -- NPC 文件位置、钩子、邻近事件以及
context.npc - Boss 与实体 --
context.boss、context.player、context.players、context.entities和context.event - 世界与环境 -- 粒子、声音、闪电、生成和
context.world - 区域与目标 -- 原生 Lua 区域、脚本工具的目标/区域/粒子和相对向量
- 示例与模式 -- 带讲解的完整工作能力
- 枚举与值 -- Particle、Material、PotionEffectType 等的 Spigot Javadoc 链接
- 故障排除 -- 常见错误、调试技巧和 QC 清单
对于基于 YAML 的脚本,EliteScript 页面仍然是权威参考:
