跳至主要内容

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 頁面仍然是權威參考: