Lua 脚本:入门指南
你将学到什么
本页将教你编写 EliteMobs 的第一个 Lua 能力,从空白文件一直到可用的 Boss 技能。结束时你将理解钩子、上下文、冷却时间以及每个 Lua 能力文件的一般结构。
熟悉基础知识后,继续阅读以下配套页面:
- 钩子与生命周期 -- 钩子名称、触发顺序和上下文可用性
- Boss 与实体 --
context.boss、context.player、context.players和context.entitiesAPI - 世界与环境 -- 粒子、声音、闪电、生成和
context.worldAPI - 区域与目标 -- 原生 Lua 区域、脚本工具目标和
context.zones/context.scriptAPI - 示例与模式 -- 可学习和改编的完整工作能力
- 枚举与值 -- Particle、Material、PotionEffectType 等字符串常量的 Spigot Javadoc 链接
- 故障排除 -- 常见错误、调试技巧和 QC 清单
Lua 能力文件目前是实验性的。随着 EliteMobs 的发展,钩子名称、辅助方法和行为可能会发生变化,因此在生产服务器上使用前请仔细测试。
什么是 Lua 能力
Lua 能力是独立的 .lua 文件,位于 EliteMobs 正常的 powers 目录树中,引用方式与普通能力文件完全相同。
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 感觉像"配置加决策"。
EliteMobs 作者的 Lua 简明入门
这是大多数能力作者需要的最少 Lua 语法。
变量
使用 local 存储值:
local cooldown_key = "fire_burst"
local damage_multiplier = 1.5
local 表示变量仅属于此文件或代码块。
函数
函数是可复用的逻辑块:
local function warn_player(player)
player:send_message("&cMove!")
end
稍后你可以调用它:
warn_player(context.player)
if 检查
当某些事情只应在某些时候发生时使用 if:
if context.player == nil then
return
end
这意味着"如果此钩子没有玩家,在此停止"。
nil
nil 表示"没有值"。这是 Lua 版本的"这里什么都没有"。
你会经常检查 nil:
if context.event ~= nil then
-- 对事件做些什么
end
~= 表示"不等于"。
表
Lua 使用表来完成多种工作:
- 列表
- 具有命名键的对象
- 最终返回的能力定义
具有命名键的表示例:
local particle = {
particle = "FLAME",
amount = 1,
speed = 0.05
}
返回能力定义
在文件末尾,你返回一个表:
return {
api_version = 1,
on_spawn = function(context)
end
}
这个返回的表就是能力文件。
注释
使用 -- 编写给人看的备注:
-- 此冷却时间防止攻击在每次命中时触发
context.cooldowns:set_local(60, "fire_burst")
你的第一个工作能力,逐步构建
如果你是完全的新手,这是通往"第一次成功"的最佳路径。
步骤 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。
步骤 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 在 powers 文件夹中附带了数十个预制 Lua 能力,如 attack_fire.lua、frost_cone.lua、meteor_shower.lua 等。这些是学习的绝佳参考 -- 只需在 plugins/EliteMobs/powers/ 目录中打开任何 .lua 文件。为了向后兼容,预制能力也以其旧版 .yml 名称注册。
最小文件约定
每个 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 = 被击中的 Boss
-- context.player = 击中它的玩家
-- context.world = 生成粒子、声音等的工具
-- context.cooldowns = 冷却时间管理
-- context.state = 你自己的持久存储
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-- 区域几何、目标、伤害、粒子。请参阅区域与目标。
方法语法:: 与 .
在 Lua 中,object:method(arg) 是 object.method(object, arg) 的简写。EliteMobs API 接受两种形式,因此两者都有效:
context.cooldowns:set_local(60, "test")
context.cooldowns.set_local(60, "test") -- 相同
所有文档统一使用 :。
后续步骤
现在你已经了解了基础知识,探索 Lua 脚本文档的其余部分:
- 钩子与生命周期 -- 钩子名称、执行顺序以及每个钩子中可用的上下文键
- Boss 与实体 --
context.boss、context.player、context.players、context.entities和context.event - 世界与环境 -- 粒子、声音、闪电、生成和
context.world - 区域与目标 -- 原生 Lua 区域、脚本工具的目标/区域/粒子和相对向量
- 示例与模式 -- 带讲解的完整工作能力
- 枚举与值 -- Particle、Material、PotionEffectType 等的 Spigot Javadoc 链接
- 故障排除 -- 常见错误、调试技巧和 QC 清单
对于基于 YAML 的脚本,EliteScript 页面仍然是权威参考:
