跳到主要内容

Lua 脚本:入门指南

webapp_banner.jpg

你将学到什么

本页将教你编写 EliteMobs 的第一个 Lua 能力,从空白文件一直到可用的 Boss 技能。结束时你将理解钩子、上下文、冷却时间以及每个 Lua 能力文件的一般结构。

熟悉基础知识后,继续阅读以下配套页面:

  • 钩子与生命周期 -- 钩子名称、触发顺序和上下文可用性
  • NPC 脚本 -- NPC 生命周期、交互和邻近钩子
  • Boss 与实体 -- context.bosscontext.playercontext.playerscontext.entities API
  • 世界与环境 -- 粒子、声音、闪电、生成和 context.world API
  • 区域与目标 -- 原生 Lua 区域、脚本工具目标和 context.zones / context.script API
  • 示例与模式 -- 可学习和改编的完整工作能力
  • 枚举与值 -- Particle、Material、PotionEffectType 等字符串常量的 Spigot Javadoc 链接
  • 故障排除 -- 常见错误、调试技巧和 QC 清单
仍在演进中

EliteMobs 自带的内置能力就是用 Lua 编写的,所以这套系统已经在实际使用中——但辅助方法的名称和行为在不同版本之间仍有可能变化。在把自定义 Lua 能力上线到生产环境之前,请先在预发布服务器上测试,并在更新之后重新查看本页。

与 EliteScript 的关系

Lua 不会替代现有的 eliteScript: YAML 系统,而且 EliteScript 也没有被弃用。

  • 当你需要使用现有的动作目标区域条件冷却时间相对向量页面进行声明式 YAML 驱动脚本时,请使用 EliteScript
  • 当你需要变量、循环、随机选择、可复用辅助函数、每个 Boss 的持久状态和更传统的编程流程时,请使用 Lua 能力文件

两者的关系比看起来更紧密:脚本工具 API(context.script)会把你的规格表交给真正的 EliteScript 引擎,因此 EliteScript 各页面所记录的每一个目标、区域、相对向量和粒子字段,在 Lua 中都可以逐字照用。花在学习 EliteScript 上的时间完全不会浪费。


什么是 Lua 能力

Lua 能力是独立的 .lua 文件,位于 EliteMobs 正常的 powers 目录树中,引用方式与普通能力文件完全相同。

Boss 能力 vs. NPC 脚本

本页讲的是 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_spawnon_boss_damaged_by_player
Cooldownscontext.cooldowns(见下方 context.cooldowns
Actions直接方法调用如 context.world:spawn_particle_at_location(...)context.script:damage(...)
Targetscontext.script:target({...}) -- 使用 EliteScript Target 字段名
Zonescontext.script:zone({...}) 或原生 context.zones 辅助 -- 见区域与目标
Relative vectorscontext.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

完整的新手流程是:

  1. 将文件保存到 plugins/EliteMobs/powers/
  2. .lua 文件名添加到 Boss 的 powers: 列表
  3. 生成或重新加载该 Boss
  4. 测试你正在构建的钩子

如果需要更多关于 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 能力时,使用以下顺序:

  1. 创建文件并让 on_spawn 工作。
  2. 切换到你实际需要的钩子。
  3. 确认钩子具有你期望的数据,如 context.playercontext.event
  4. 先添加消息或声音,在伤害或粒子之前。
  5. 添加冷却时间。
  6. 添加一个游戏效果。
  7. 之后才添加辅助函数、状态、调度器逻辑或区域。

这个顺序使调试大大简化,因为每次只改变一件事。


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.luapowers/bosses/fire.lua,因为一个可能在发现过程中覆盖另一个。

预制 Lua 能力

EliteMobs 自带的内置能力现在全部用 Lua 编写。其中约七十个会在首次启动时写入 plugins/EliteMobs/powers/ —— attack_fire.luafrost_cone.luameteor_shower.luashield_wall.lua 等等。它们是现有最好的参考资料:打开该目录中的任意 .lua 文件读一读即可。

预制文件只有在缺失时才会被写入,因此你对它们所做的修改能在重启后保留。删除某个文件即可让 EliteMobs 重新生成原版。

旧的 .yml 名称仍然可以解析

引用旧的 attack_fire.yml 的 Boss 配置仍然可用。当某个能力按 .yml(或 .yaml)名称查找时,EliteMobs 会先检查是否存在同名基名的 .lua 文件并优先使用它,否则再回退到字面名称。换句话说,.lua 能力会覆盖同名的 .yml 能力 —— 迁移之后你无需修改 Boss 文件,并且可以通过在内置能力旁放置你自己的 .lua 来覆盖它。


最小文件约定

每个 Lua 能力必须用 return 返回一个表。该表有意设计得很严格。

必需和可选的顶级字段

字段必需类型备注
api_versionNumber当前必须为 1
priorityNumber执行优先级。较低的值先执行。默认为 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_spawnon_boss_damaged_by_player

最常用的入门钩子是 on_spawnon_boss_damaged_by_playeron_enter_combaton_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.bosscontext.playercontext.playerscontext.entitiescontext.event
  • 世界与环境 -- 粒子、声音、闪电、生成和 context.world
  • 区域与目标 -- 原生 Lua 区域、脚本工具的目标/区域/粒子和相对向量
  • 示例与模式 -- 带讲解的完整工作能力
  • 枚举与值 -- Particle、Material、PotionEffectType 等的 Spigot Javadoc 链接
  • 故障排除 -- 常见错误、调试技巧和 QC 清单

对于基于 YAML 的脚本,EliteScript 页面仍然是权威参考: