跳至主要內容

Lua 腳本:疑難排解

webapp_banner.jpg

本頁涵蓋撰寫或除錯 Lua 能力時可能遇到的常見問題,以及給從 EliteScript 轉移過來的作者的遷移建議。如果你正在除錯 NPC 腳本,請參閱 NPC 腳本。如果你正在尋找可用的範例,請參閱範例與模式。如果你剛剛起步,請參閱入門指南

共用 Lua 引擎

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

只有底層事件本身帶有玩家的鉤子才會填入 context.player。它在 on_spawnon_game_tickon_boss_damagedon_boss_damaged_by_eliteon_exit_combaton_healon_deathon_phase_switch永遠為 nil。

它會在 on_boss_damaged_by_playeron_player_damaged_by_bosson_enter_combaton_boss_target_changed 中被填入,並且在 on_zone_enter / on_zone_leave 中,當涉及的實體剛好是玩家時也會被填入。使用前請務必加上 nil 守衛。完整表格請參閱鉤子與生命週期

4. 逾時 / 執行預算超限

每個鉤子、排程回呼及檔案求值都與巢狀呼叫共用預算:250,000 條 Lua 指令與 50 毫秒執行緒 CPU 時間。若 JVM 無法測量執行緒 CPU 時間,則改用 250 毫秒實際經過時間的備用限制。錯誤會指出超出的限制:

Lua instruction budget exceeded (250000 instruction limit)
Lua CPU-time budget exceeded (50ms current-thread CPU limit)
Lua elapsed-time fallback budget exceeded (250ms fallback; current-thread CPU time unavailable)

VM 會在執行途中中斷失控的 Lua 迴圈,但無法中斷正在執行的 Java API 呼叫;呼叫返回後也沒有額外的 50 毫秒限制。請使用冷卻、減少取樣點,或透過 context.scheduler:run_every(...) 將工作分散到多個 tick。

5. 排程器回呼使用了過時的資料

你很可能使用了外層的 context 而不是回呼參數。將 function() ... context.boss ... end 改為 function(tick_context) ... tick_context.boss ... end

6. 區域查詢不傳回任何實體

仔細檢查區域定義。對於原生區域,請確保 kind 為小寫("sphere",不是 "SPHERE")。對於腳本工具,請確保 shape 為大寫("CONE",不是 "cone")。同時也要確認 originTarget 確實解析為有效的位置。

7. 粒子不顯示

使用有效的 Bukkit Particle 名稱;"FLAME""flame" 都會轉換為大寫。也要檢查位置、數量及所需資料。BLOCK 需要此世界輔助方法未提供的方塊資料;請改用支援的粒子,或使用帶材質選項的腳本粒子 API。

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. 實際型別)。
預算超限訊息會指出超出的是指令數、CPU 時間或備用經過時間限制;請參閱上方說明。
提示

當你在控制台看到 [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 能力最簡單的方法是:

  1. 繼續以事件、目標、區域、相對向量和粒子的方式思考。 概念是相同的 -- 改變的只有語法。EliteScript 事件變成了像 on_spawnon_boss_damaged_by_player 這樣的鉤子名稱。目標和區域會以表的形式傳遞給 context.script,使用與 EliteScript 區域EliteScript 目標頁面中所記載的相同欄位名稱。

  2. 將你的控制流程移到 Lua 中。 隨機擲骰、共用輔助函式、迴圈、持久狀態(context.state)和任務排程(context.scheduler)正是 Lua 新增、而純 EliteScript 難以輕鬆實現的功能。先從將一個帶分支或條件的能力轉換成 Lua 開始,同時保持其餘部分不變。

  3. 使用 context.script 來處理目標選擇與區域幾何。 這些並不是外觀相似的仿製品 —— 你傳入的規格表會被交給實際的 EliteScript 引擎,因此 EliteScript 頁面所記載的每個欄位(targetTypeshapeTargetTarget2FinalTargetrangeoffsetrelativeOffsetcoveragefilter⋯⋯)都能逐字使用,並具有相同的預設值與相同的大小寫。請把 EliteScript 文件開著作為規格參考,而 Lua 純粹用於邏輯層。

  4. 留意兩套區域系統。 context.script:zone({shape = "SPHERE", ...}) 是 EliteScript 引擎(大寫列舉、Target 規格表)。context.zones 則是另一套獨立的輕量實作(小寫 kind、單純的 origindestination 位置)。混用它們的鍵風格會靜默產生一個空區域。


初學者進階路徑

如果你想從零開始學習這套系統,以下進階順序效果很好:

  1. 寫一個只包含 api_version = 1on_spawn 的檔案。
  2. 讓 Boss 傳送訊息或播放聲音。
  3. context.cooldowns 加上冷卻。
  4. 加入一個由玩家觸發的鉤子,例如 on_boss_damaged_by_player
  5. context.scheduler:run_after(...) 加入一個延遲動作。
  6. 加入一個簡單的原生 Lua 區域查詢,或一個簡單的 context.script:target(...)
  7. 在那之後才進入輪替攻擊、狀態機和多步驟機制。

每個步驟都建立在前一個之上,而且你可以在每個階段進行測試。不要試圖把多階段 Boss 當作你的第一個 Lua 能力來寫。


後續步驟

  • 入門指南 -- 檔案結構、鉤子、第一個能力的逐步演練、可複製貼上的範本
  • NPC 腳本 -- NPC 鄰近偵測、互動與生命週期腳本
  • 範例與模式 -- 可供你研究與改編的完整可用能力