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 清单
EliteMobs 自带的内置能力就是用 Lua 编写的,所以这套系统已经在实际使用中——但辅助方法的名称和行为在不同版本之间仍有可能变化。在把自定义 Lua 能力上线到生产环境之前,请先在预发布服务器上测试,并在更新之后重新查看本页。
Lua 不会替代现有的 eliteScript: YAML 系统,而且 EliteScript 也没有被弃用。
- 当你需要使用现有的动作、目标、区域、条件、冷却时间和相对向量页面进行声明式 YAML 驱动脚本时,请使用 EliteScript。
- 当你需要变量、循环、随机选择、可复用辅助函数、每个 Boss 的持久状态和更传统的编程流程时,请使用 Lua 能力文件。
两者的关系比看起来更紧密:脚本工具 API(context.script)会把你的规格表交给真正的 EliteScript 引擎,因此 EliteScript 各页面所记录的每一个目标、区域、相对向量和粒子字段,在 Lua 中都可以逐字照用。花在学习 EliteScript 上的时间完全不会浪费。
什么是 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 极简入门
要编写 EliteMobs 能力,你不需要成为 Lua 专家。大多数能力只用到少数几个概念:变量(local x = 5)、函数(function foo() end)、if 检查(if x then ... end)、表({key = value})以及 nil(Lua 表示"什么都没有"的值)。语法很轻量 —— 没有分号,没有花括号,只用 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:列表 - 生成或重新加载该 Boss
- 测试你正在构建的钩子
如果需要更多关于 Boss 文件、能力列表或自定义 Boss 结构的背景,请参阅创建自定义 Boss。那个页面详细介绍了 Boss 的搭建,包括 powers: 列表是如何工作的。
步骤 1:让文件加载
return {
api_version = 1,
on_spawn = function(context)
end
}
如果加载没有错误,你已经证明了:
- 文件是有效的 Lua
- EliteMobs 找到了它
- 返回表的结构是正确的
on_spawn是有效的钩子名称
步骤 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
}
这是大多数作者需要的第一个真正有用的模式。如果你理解了这个模式,就已经可以构建许多实用的能力了。
步骤 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.player:send_message("&cStatic jumps from the boss into your armor!")
context.world:strike_lightning_at_location(context.player.current_location)
context.cooldowns:set_local(100, "shock")
end
}
到这里,你已经在编写真正的能力了。
第一个真实工作流程
从头构建新的 Lua 能力时,使用以下顺序:
- 创建文件并让
on_spawn工作。 - 切换到你实际需要的钩子。
- 确认钩子具有你期望的数据,如
context.player或context.event。 - 先添加消息或声音,在伤害或粒子之前。
- 添加冷却时间。
- 添加一个游戏效果。
- 之后才添加辅助函数、状态、调度器逻辑或区域。
这个顺序使调试大大简化,因为每次只改变一件事。
Lua 文件的位置
将 .lua 文件放在与普通能力 .yml 文件相同的文件夹树中:
plugins/
EliteMobs/
powers/
mycoolpower.lua
attack_push.yml
subfolder/
myotherpower.lua
Lua 能力会从 EliteMobs 已经加载的能力目录中自动发现。
Boss 如何引用 Lua 能力
Boss 文件仍然使用普通的 powers: 列表:
powers:
- attack_push.yml
- mycoolpower.lua
不需要特殊字段。Lua 能力不通过 eliteScript: 加载。
文件命名规则
- Boss 通过文件名引用 Lua 能力,而非文件夹路径。
- EliteMobs 目前仅通过基本名称注册发现的 Lua 能力。
- 避免重复的名称如
powers/fire.lua和powers/bosses/fire.lua,因为一个可能在发现过程中覆盖另一个。
预制 Lua 能力
EliteMobs 自带的内置能力现在全部用 Lua 编写。其中约七十个会在首次启动时写入 plugins/EliteMobs/powers/ —— attack_fire.lua、frost_cone.lua、meteor_shower.lua、shield_wall.lua 等等。它们是现有最好的参考资料:打开该目录中的任意 .lua 文件读一读即可。
预制文件只有在缺失时才会被写入,因此你对它们所做的修改能在重启后保留。删除某个文件即可让 EliteMobs 重新生成原版。
旧的 .yml 名称仍然可以解析
引用旧的 attack_fire.yml 的 Boss 配置仍然可用。当某个能力按 .yml(或 .yaml)名称查找时,EliteMobs 会先检查是否存在同名基名的 .lua 文件并优先使用它,否则再回退到字面名称。换句话说,.lua 能力会覆盖同名的 .yml 能力 —— 迁移之后你无需修改 Boss 文件,并且可以通过在内置能力旁放置你自己的 .lua 来覆盖它。
最小文件约定
每个 Lua 能力必须用 return 返回一个表。该表有意设计得很严格。
必需和可选的顶级字段
| 字段 | 必需 | 类型 | 备注 |
|---|---|---|---|
api_version | 是 | Number | 当前必须为 1 |
priority | 否 | Number | 执行优先级。较低的值先执行。默认为 0 |
| 支持的钩子键 | 否 | Function | 必须使用钩子参考中列出的确切钩子名称之一 |
验证规则
- 文件必须返回一个表。
api_version是必需的,当前必须为1。priority如果存在必须是数字。- 每个额外的顶级键必须是支持的钩子名称。
- 每个钩子键必须指向一个函数。
- 未知的顶级键会被拒绝。
这意味着辅助函数和局部常量应该放在最终 return 的上方,而非放在返回的表内部,除非它们是实际的钩子。
复制粘贴入门模板
最小的有效 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.state 中存储数据,并在不同的钩子中稍后读取。
关键 context API
以下是你将从 context 使用的最重要的 API:
-
context.state-- 在 Boss 生命周期内持续存在的简单 Lua 表。用于存储阶段编号、任务 ID、标志以及你需要在钩子之间记住的任何内容。只有context.state持续存在 -- 所有其他上下文表在每次调用时重新创建。 -
context.log-- 使用log:info(msg)、log:warn(msg)和log:debug(msg)进行控制台日志记录。在开发过程中非常宝贵。 -
context.cooldowns-- 每个能力的本地冷却和每个 Boss 的全局冷却。关键方法是cooldowns:check_local(key, ticks),它原子地检查并设置冷却时间。完整的冷却 API 请参阅钩子与生命周期页面。 -
context.scheduler-- 使用scheduler:run_after(ticks, callback)和scheduler:run_every(ticks, callback)的延迟和重复任务。回调接收新的 context -- 始终使用回调参数,而非外部context。在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) 必须使用点号;冒号会把事件表作为第一个参数传入。
后续步骤
现在你已经了解了基础知识,探索 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 页面仍然是权威参考:
