Lua 腳本:疑難排解
本頁涵蓋撰寫或除錯 Lua 能力時可能遇到的常見問題,以及給從 EliteScript 轉移過來的作者的遷移建議。如果你正在除錯 NPC 腳本,請參閱 NPC 腳本。如果你正在尋找可用的範例,請參閱範例與模式。如果你剛剛起步,請參閱入門指南。
EliteMobs 使用在各 Nightbreak 插件之間共用的 MagmaCore Lua 腳本引擎。關於沙箱、排程器、區域、世界 API、實體表與玩家 UI 方法等共用概念的說明,請參閱 MagmaCore Lua 腳本引擎頁面。
常見問題
1. 能力完全無法載入
檢查伺服器啟動時控制台中的錯誤。最常見的原因是 Lua 語法錯誤(缺少 end、括號不匹配等)。同時也要確認檔案以 .lua 結尾,並放置在正確的 powers 目錄中。
2. 鉤子從未觸發
確認鉤子名稱與鉤子列表中所列的拼寫完全一致。常見錯誤:on_boss_hit(錯誤)vs. on_boss_damaged_by_player(正確),或 on_tick(錯誤)vs. on_game_tick(正確)。
3. context.player 為 nil
並非所有鉤子都會提供玩家。on_spawn、on_game_tick 和 on_exit_combat 沒有玩家。on_enter_combat 確實會提供 context.player(觸發戰鬥的玩家)。在 on_boss_damaged(通用傷害)中,造成傷害者可能不是玩家。在使用 context.player 之前,請務必加上 nil 守衛。
4. 逾時 / 執行預算超限
如果某個鉤子或回呼耗時過長,能力會被自動停用以防止延遲。控制台訊息看起來像這樣:
[Lua] my_power.lua took 73ms in 'on_game_tick' (limit: 50ms) — script disabled to prevent lag.
常見原因:迭代過多實體、每 tick 建立過多區域,或在 on_game_tick 中執行昂貴的字串運算。請將昂貴的工作放在冷卻閘門之後,或減少每次呼叫所做的工作。
5. 排程器回呼使用了過時的資料
你很可能使用了外層的 context 而不是回呼參數。將 function() ... context.boss ... end 改為 function(tick_context) ... tick_context.boss ... end。
6. 區域查詢不傳回任何實體
仔細檢查區域定義。對於原生區域,請確保 kind 為小寫("sphere",不是 "SPHERE")。對於腳本工具,請確保 shape 為大寫("CONE",不是 "cone")。同時也要確認 origin 或 Target 確實解析為有效的位置。
7. 粒子不顯示
確認粒子名稱是有效的 Bukkit Particle 列舉值且為 UPPER_CASE。常見錯誤:"flame"(錯誤)vs. "FLAME"(正確)。同時確認 amount 至少為 1,且該位置位於已載入的區塊中。
8. 冷卻似乎不起作用
請確保你使用的是 check_local(key, duration)(在單次呼叫中同時檢查並設定),而不是 local_ready(key) 後接一個獨立的 set_local(duration, key)。如果你只單獨使用 local_ready,那麼你只是進行了檢查,卻從未設定冷卻。
9. Boss 在死亡後仍持續執行能力
在 on_exit_combat 和/或 on_death 中加入清理邏輯以取消排程器任務。如果 Boss 死亡,on_exit_combat 應該會觸發,但在兩個鉤子中都加上明確的清理會更安全。
閱讀錯誤訊息
當 Lua 能力出現問題時,控制台會列印出以 [Lua] 為前綴的友善錯誤區塊。這些訊息會以淺白的英文準確告訴你是哪個檔案、哪一行、哪個鉤子,以及出了什麼問題。除錯前請務必先閱讀完整訊息。
典型的錯誤看起來像這樣:
[Lua] Error in 'push_zone.lua' at line 35 during 'on_boss_damaged_by_player':
[Lua] -> You tried to call a method or function that doesn't exist.
[Lua] -> Check the method name for typos, or make sure you're using ':' (colon) for method calls, not '.' (dot).
[Lua] -> Script has been disabled for this entity to prevent further errors.
系統會將常見的 Lua 錯誤翻譯成淺白的英文。以下是最常見的幾種:
| 原始 Lua 錯誤 | 控制台告訴你的內容 |
|---|---|
attempt to call nil | 你嘗試呼叫一個不存在的方法或函式。檢查方法名稱是否有拼字錯誤,或確認你是用 :(冒號)而非 .(點)來進行方法呼叫。 |
index expected, got nil | 你嘗試存取某個為 nil 的東西上的欄位。檢查先前的程式碼是否已將其初始化。 |
attempt to index | 你嘗試存取 nil 或無效值上的屬性。 |
bad argument | 顯示具體的參數不匹配細節(預期型別 vs. 實際型別)。 |
| Timeout | <filename> 在 'hook_name' 中耗時 Xms(限制:50ms)-- 腳本已停用以防止延遲。 |
當你在控制台看到 [Lua] 錯誤時,錯誤訊息會以淺白的英文準確告訴你是哪個檔案、哪一行、哪個鉤子,以及出了什麼問題。在深入研究程式碼之前先閱讀完整訊息 -- 它通常會直接指向解決方法。
不要假設未文件化的別名存在
Lua API 公開的是一組特定的方法名稱。如果你正在手動撰寫能力,或藉助 AI 協助撰寫,請不要假設存在簡寫或替代名稱。以下是一些並不存在且會導致錯誤的名稱範例:
show_temporary_boss_bar()-- 請改用player:show_boss_bar(title, color, style, duration)。run_command_as_player()-- 請改用player:run_command(command)。em.location(...)-- 方法名稱錯誤。請改用em.create_location(x, y, z),或context.boss:get_location()/context.player.current_location。em.vector(...)-- 方法名稱錯誤。請改用em.create_vector(x, y, z),或單純的{x=0, y=1, z=0}表。em.zone.sphere(...)-- 方法名稱錯誤。請改用em.zone.create_sphere_zone(radius),或一個區域定義表如{kind = "sphere", radius = 5, origin = location}。entity:teleport_to(...)-- 請改用entity:teleport_to_location(location)。entity:set_velocity(...)-- 請改用entity:set_velocity_vector(vector)。entity:set_facing(...)-- 請改用entity:face_direction_or_location(direction_or_location)。
如有疑問,請查看 API 參考頁面(Boss 與實體、世界與環境、區域與目標選擇)。如果那裡沒有文件記載,它就不存在。
給 EliteScript 作者的遷移建議
如果你已經能寫出不錯的 EliteScript,學習 Lua 能力最簡單的方法是:
-
繼續以事件、目標、區域、相對向量和粒子的方式思考。 概念是相同的 -- 改變的只有語法。EliteScript 事件變成了像
on_spawn或on_boss_damaged_by_player這樣的鉤子名稱。目標和區域會以表的形式傳遞給context.script,使用與 EliteScript 區域和 EliteScript 目標頁面中所記載的相同欄位名稱。 -
將你的控制流程移到 Lua 中。 隨機擲骰、共用輔助函式、迴圈、持久狀態(
context.state)和任務排程(context.scheduler)正是 Lua 新增、而純 EliteScript 難以輕鬆實現的功能。先從將一個帶分支或條件的能力轉換成 Lua 開始,同時保持其餘部分不變。 -
使用
context.script來處理目標選擇與區域幾何。 腳本工具接受與 EliteScript 相同的欄位名稱(targetType、shape、Target、Target2、range、offset、coverage),因此你可以繼續使用現有的 EliteScript 文件作為這些規格的參考。這讓你能在運用熟悉模式的同時,於邏輯層獲得 Lua 的彈性。
初學者進階路徑
如果你想從零開始學習這套系統,以下進階順序效果很好:
- 寫一個只包含
api_version = 1和on_spawn的檔案。 - 讓 Boss 傳送訊息或播放聲音。
- 用
context.cooldowns加上冷卻。 - 加入一個由玩家觸發的鉤子,例如
on_boss_damaged_by_player。 - 用
context.scheduler:run_after(...)加入一個延遲動作。 - 加入一個簡單的原生 Lua 區域查詢,或一個簡單的
context.script:target(...)。 - 在那之後才進入輪替攻擊、狀態機和多步驟機制。
每個步驟都建立在前一個之上,而且你可以在每個階段進行測試。不要試圖把多階段 Boss 當作你的第一個 Lua 能力來寫。
