跳至主要內容

Lua 腳本:疑難排解

webapp_banner.jpg

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

共用 Lua 引擎

EliteMobs 使用在各 Nightbreak 插件之間共用的 MagmaCore Lua 腳本引擎。關於沙箱、排程器、區域、世界 API、實體表與玩家 UI 方法等共用概念的說明,請參閱 MagmaCore Lua 腳本引擎頁面。


常見問題​

1. 能力完全無法載入​

檢查伺服器啟動時控制台中的錯誤。最常見的原因是 Lua 語法錯誤(缺少 end、括號不匹配等)。同時也要確認檔案以 .lua 結尾,並放置在正確的 powers 目錄中。

對於自訂檔名,也要檢查同目錄、同基本名稱的 .yml 或 .yaml 中繼資料是否設定了 isEnabled: false;這會阻止發現該檔案。內建能力旁的同基本名稱中繼資料檔,也會將其 isEnabled、effect、powerType 與冷卻欄位套用到該能力。若不同資料夾中有兩個同名的能力檔案,只會載入其中一個;主控台會指出被略過的副本。

檔案必須傳回包含 api_version = 1 的表。該表只能包含 api_version、選填的 priority 和支援的鉤子名稱;鉤子值必須是函式。輔助函式應保持為區域函式,不要在傳回表加入任意鍵。頂層程式碼會在定義驗證和執行階段初始化時執行,此時沒有鉤子上下文。

2. 鉤子從未觸發​

確認鉤子名稱與鉤子列表中所列的拼寫完全一致。常見錯誤:on_boss_hit(錯誤)vs. on_boss_damaged_by_player(正確),或 on_tick(錯誤)vs. on_game_tick(正確)。

傳回表中的不支援鍵會阻止整個定義載入。如果已載入但沒有執行,也要檢查 Boss 是否參照能力檔名、對應事件是否發生,以及執行個體是否已失敗、暫停或被抑制了能力。

3. context.player 為 nil​

只有底層事件本身帶有玩家的鉤子才會填入 context.player。它在 on_spawn、on_game_tick、on_boss_damaged、on_boss_damaged_by_elite、on_exit_combat、on_heal、on_death、on_phase_switch 與 on_mind_action 中永遠為 nil。

它會在 on_boss_damaged_by_player、on_reinforcement_damaged_by_player、on_player_damaged_by_boss、on_enter_combat 與 on_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 毫秒實際經過時間限制。常見原因包括遍歷過多實體、未設定 coverage 就在大區域生成粒子,以及每 tick 執行耗費資源的操作。請使用冷卻、減少取樣位置,或透過 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")。同時也要確認 origin 或 Target 確實解析為有效的位置。

也要檢查實體篩選器和世界。Boss 原生區域查詢預設使用 living,排除 Boss 自身並搜尋其所在世界。腳本工具區域預設使用 PLAYER。透過區域控制代碼的 full_target() 或 border_target() 取得 Lua 區域目標;單獨的 context.script:target({targetType = "ZONE_FULL"}) 沒有關聯區域。

7. 粒子不顯示​

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

8. 冷卻似乎不起作用​

check_local(key, duration) 在一次呼叫中檢查並設定冷卻。也可以先用 local_ready(key),再於攻擊真正開始時呼叫 set_local(duration, key);只檢查是否就緒不會啟動冷卻。同一個 Boss 的各能力共用明確指定的鍵,因此請使用能力專屬前綴。YAML 中繼資料不會自動限制 Lua 鉤子的執行。

9. Boss 在死亡後仍持續執行能力​

能力執行個體關閉時會取消 context.scheduler 所屬任務。若攻擊迴圈必須在移除之前停止,請在 on_exit_combat 中取消它。也要檢查所呼叫的輔助方法,有些 Java 輔助方法會啟動自己的任務。例如,world:run_fake_gold_nugget_damage(...) 已執行一個帶獨立逾時的逐 tick 迴圈,每批投射物只能呼叫一次。


閱讀錯誤訊息​

Lua 能力在執行時失敗,控制台會輸出帶 [Lua] 前綴的錯誤區塊,標明檔案和鉤子或回呼。若能從原始錯誤擷取行號,也會顯示行號。除錯前請閱讀完整訊息。對於使用 -- @include 的檔案,行號對應展開後的原始碼。

典型的錯誤看起來像這樣:

[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 時間或備用經過時間限制;請參閱上方說明。
提示

失敗的腳本實例會針對該實體被停用。修正檔案後,必須重新載入定義並建立新的執行個體;未變更的已停用實例不會每 tick 重試失敗的鉤子。


不要假設未文件化的別名存在​

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)。
  • 對於 EliteMobs 的 Boss 專用實體包裝器,請使用 entity:set_velocity_vector(vector)。共用的 MagmaCore 包裝器也提供 set_velocity(...);請檢查上下文傳回的是哪種包裝器。
  • entity:set_facing(...) -- 請改用 entity:face_direction_or_location(direction_or_location)。

請查閱所用包裝器的 API 參考:Boss 與實體、世界與環境、區域與目標選擇或共用 API。相似的上下文也可能提供不同方法;一個看似合理的別名不一定能用。


給 EliteScript 作者的遷移建議​

如果你已經能寫出不錯的 EliteScript,學習 Lua 能力最簡單的方法是:

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

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

  3. 使用 context.script 處理目標選擇與區域幾何。 其規格表透過實際的 EliteScript 解析器處理 targetType、shape、Target、Target2、FinalTarget、range、offset、relativeOffset、coverage 和 filter。欄位名稱和預設值可查閱 EliteScript 文件,同時請閱讀 Lua 控制代碼限制。靜態 Lua 控制代碼不執行 YAML 動作的 track: false 快取,也沒有繼承的動作資料。offset 與 relativeOffset 會套用到解析出的位置,包括 LOCATION、LOCATIONS 與取樣的區域位置。區域必須固定時,請明確使用固定原點。

  4. 留意兩套區域系統。 context.script:zone({shape = "SPHERE", ...}) 使用 EliteScript 引擎、大寫列舉和 Target 規格表。context.zones 使用小寫 kind 和普通的 origin/destination 位置。混用這些鍵格式可能產生空區域或非預期區域、警告或執行階段錯誤。


初學者進階路徑​

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

  1. 寫一個只包含 api_version = 1 和 on_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 鄰近偵測、互動與生命週期腳本
  • 範例與模式 -- 可供你研究與改編的完整可用能力