跳到主要内容

Lua 脚本:入门指南

webapp_banner.jpg

你将学到什么

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

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

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

Lua 能力文件目前是实验性的。随着 EliteMobs 的发展,钩子名称、辅助方法和行为可能会发生变化,因此在生产服务器上使用前请仔细测试。

与 EliteScript 的关系

Lua 不会替代现有的 eliteScript: YAML 系统。

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

Lua 拥有自己的目标和区域系统,使用与 EliteScript 中相同的枚举名称和概念。脚本工具 API(context.script)允许你使用 EliteScript 页面中记录的相同字段名称创建区域、目标和相对向量。


什么是 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_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 感觉像"配置加决策"。


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

完整的新手流程是:

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

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

  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 在 powers 文件夹中附带了数十个预制 Lua 能力,如 attack_fire.luafrost_cone.luameteor_shower.lua 等。这些是学习的绝佳参考 -- 只需在 plugins/EliteMobs/powers/ 目录中打开任何 .lua 文件。为了向后兼容,预制能力也以其旧版 .yml 名称注册。


最小文件约定

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

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