跳至主要內容

Lua 腳本:疑難排解

本頁介紹撰寫或除錯 FreeMinecraftModels 道具腳本時常見的問題,以及延遲產生配置的使用方式。實際範例請見範例與模式;剛開始使用時請見入門指南。

MagmaCore Lua 執行環境

FMM 使用 MagmaCore 的 ScriptInstance 執行環境。關於 FMM/MagmaCore 的沙盒、排程器、區域、world API、實體表格與玩家 UI 方法的說明文件,請參閱 MagmaCore Lua 腳本引擎頁面。EliteMobs 的 Boss 能力與 NPC 腳本重用同一個 Lua 沙盒,但提供各自的 context 表格。


常見問題​

1. 配置檔未載入 / 腳本未關聯​

症狀: 道具已生成,但不回應點擊或任何鉤子。

原因和解決方法:

  • 尚不存在 .yml 配置檔。 FMM 會在首次生成道具時嘗試非同步建立預設配置(已啟用、無腳本),且不會覆寫既有檔案。加入腳本名後,成功完成 /fmm reload,再放置新道具。修改 YAML 與 Lua 都需要重新載入。

  • 配置中設定了 isEnabled: false。 開啟模型檔案旁邊的 .yml 檔案,將其設定為 isEnabled: true。

  • scripts: 列表為空。 新增你的腳本檔名:

    isEnabled: true
    scripts:
    - my_script.lua
  • .yml 檔名與模型檔名不符。 配置必須與模型檔案具有相同的基本名稱。例如,torch_01.fmmodel 需要同一目錄下的 torch_01.yml。


2. 找不到腳本檔案​

症狀: 主控台顯示:[FMM Scripts] Script 'my_script.lua' not found in scripts/ folder

原因和解決方法:

  • 目錄錯誤。 腳本檔案必須位於 plugins/FreeMinecraftModels/scripts/,而不是模型檔案旁邊。

  • 檔名不符。 .yml 中的名稱必須與 scripts/ 中的檔名大小寫一致。建議填寫 .lua 副檔名,但省略時 FMM 會補上。

  • 檔案不存在。 請仔細檢查 .lua 檔案是否確實存在於 scripts/ 資料夾中。

  • 快取定義或重複檔名。 FMM 在提供者初始化時遞迴搜尋腳本,但依檔名關聯,不使用相對路徑。各子資料夾中的名稱必須唯一。新增或修改 Lua 檔案後,須成功執行 /fmm reload;僅重新生成道具不會重讀快取的 Lua 程式碼。


3. 腳本完全無法載入​

症狀: 沒有主控台錯誤,但沒有鉤子觸發。

檢查伺服器主控台在啟動時是否有 Lua 語法錯誤。最常見的原因是:

  • 缺少 end 來關閉函式或 if 區塊
  • 括號不配對
  • 回傳的表中鉤子定義之間缺少逗號
  • 檔案不以 .lua 結尾

探索檔案時的失敗會顯示為無效或已停用腳本的警告,或載入錯誤。鉤子和回呼錯誤使用 [Lua] 區塊,包含檔名與執行環境;只有能從例外取得行號時才會顯示行號。


4. 鉤子從未觸發​

症狀: 你新增的啟動日誌已出現,但某個鉤子始終未觸發。

驗證鉤子名稱是否與鉤子參考完全一致。常見錯誤:

錯誤名稱正確名稱
on_clickon_right_click 或 on_left_click
on_interacton_right_click
on_hiton_left_click
on_tickon_game_tick
on_removeon_destroy(僅在道具被永久移除時執行,區塊卸載或重新載入時不會執行)
on_arrow_hiton_projectile_hit

確認模型已載入、具有可用的碰撞箱,且保護監聽器未在道具回呼前取消互動。生成的道具使用盔甲座;變更配置選項不能讓無效或未載入的模型變得可點擊。


5. 逾時 / 超出執行預算​

症狀: 主控台顯示由 Lua VM 內部拋出的錯誤。呼叫超出預算時,會在執行過程中被中止,並顯示以下訊息之一:

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)

每個鉤子、回呼或檔案求值最多執行 250,000 條 Lua 指令,或消耗 50 ms 目前執行緒 CPU 時間,以先達到的限制為準。巢狀呼叫共用同一預算。如果 JVM 無法提供每執行緒 CPU 計時,則改用 250 ms 實際經過時間的限制;只有此時才會出現第三條訊息。這表示腳本在一次呼叫中執行了過多工作。常見原因包括:

  • 在 on_game_tick 中遍歷過多實體
  • 每 tick 建立過多粒子
  • 執行高負荷迴圈,沒有將工作分散到多個 tick

修正: 用 context.cooldowns 限制高負荷工作,或使用間隔合理的 scheduler:run_repeating() 代替 on_game_tick。

指令限制約束 Lua 迴圈。時間檢查每 1024 條 Lua 指令及返回時執行,不會中途打斷阻塞的 Java/Bukkit 方法。也應限制每個回呼內部的工作量。


6. 點擊鉤子中 context.event 為 nil​

這在 on_left_click 和 on_right_click 中通常不應該發生,但始終使用以下方式進行保護:

if context.event then
context.event.cancel()
end

在排程器回呼內部,context.event 始終為 nil——這是預期行為。事件修改只能在事件鉤子本身內部進行。


7. 動畫不播放​

症狀: play_animation() 回傳 false,或者沒有可見的變化。

原因和解決方法:

  • 動畫名稱錯誤。 名稱必須與模型檔案中定義的完全相符。檢查你的 .bbmodel 或 .fmmodel 檔案以取得正確的動畫名稱。

  • 模型沒有動畫。 並非所有模型都有動畫。驗證模型檔案是否確實包含動畫資料。

  • 玩家缺少資源包或在模型顯示範圍之外。 玩家需要目前的資源包及可見的模型。

  • 請求排隊或遭抑制。 blend 傳入 true 使用單一佇列位置,不是交叉淡入淡出。後續請求可能取代它,自訂動畫期間的攻擊請求也可能回傳 true 而不打斷動畫。使用動畫列出的位置引數,區分請求被接受與實際畫面已顯示。


8. 粒子不出現​

  • 粒子名稱接受 FLAME 和 flame;API 會統一大小寫後查找 Bukkit 粒子。
  • 驗證位置位於已載入的區塊中。如果附近沒有玩家,區塊可能已卸載。
  • 驗證 count 至少為 1。
  • 某些粒子(如 DUST)需要特定的額外資料,基本的 spawn_particle() 可能不支援。使用標準粒子如 FLAME、HEART、HAPPY_VILLAGER、NOTE、ENCHANT 等。

9. 聲音不播放​

  • 聲音接受不區分大小寫的 Bukkit 列舉名稱,也接受 minecraft:block.note_block.harp 等有效鍵和自訂資源包的聲音鍵。用戶端資源包中必須包含該聲音。
  • 驗證位置座標是否正確(不全為零或 NaN)。
  • 驗證音量大於 0。

10. 狀態意外重置​

context.state 屬於一個腳本實例,在該實例運作期間保留。同一道具的 scripts: 清單中,不同項目不共用狀態。如果似乎重置了:

  • 道具可能已被移除並重新生成,或其區塊曾卸載後再次載入。每次生成或區塊載入都會建立新實例。必須在這種情況下保留的值,請用 prop:set_persistent_data 儲存。
  • 成功重新載入或腳本錯誤可能關閉了舊實例。查看主控台;僅修改檔案不會恢復已關閉的實例,仍須重新載入並關聯。
  • 你可能在排程器回呼中使用了錯誤的上下文變數來讀取狀態。始終使用回呼自身的上下文參數。

11. os 函式庫無法使用​

症狀: 腳本崩潰並出現類似 attempt to index nil (os) 的錯誤。

os 函式庫已從 Lua 沙盒中完全移除。你無法使用 os.time()、os.clock() 或任何其他 os 函式。

解決方法: 使用 context.world:get_time() 取得世界時間,或使用狀態旗標搭配 on_game_tick 的 tick 計數器手動追蹤經過時間。至於冷卻,請優先使用 context.cooldowns:check_local(key, ticks)。


12. 物品腳本沒有啟動​

裝備物品的腳本格式已停用。包含非空 scripts: 清單或舊版無命名空間附魔的自訂物品會被略過並記錄日誌。請改用目前的自訂物品定義及含命名空間的附魔。道具腳本仍使用 scripts:。

13. 物品定義無法使用​

查看啟動或重新載入警告中的確切檔名。不支援的材質,例如舊伺服器上的新版 Minecraft 物品,也會讓定義被略過。請依伺服器版本修正或替換定義;FMM 不會改寫它或自行選擇替代材質。

14. 鉤子有觸發,但腳本在呼叫實體方法時崩潰​

症狀: on_right_click 等鉤子可運作,但排程回呼中呼叫 entity:damage() 或 entity:push() 時出現 attempt to call nil 等錯誤。

原因: context.world:get_nearby_entities() 回傳範圍內所有實體,包括掉落物、經驗球和範圍效果雲等非生物實體。非生物實體沒有 damage()、push() 或 add_potion_effect() 等生物方法。

解決方法: 在呼叫生物實體方法之前,一律先用 if entity.damage then 保護:

local entities = context.world:get_nearby_entities(x, y, z, radius)
for _, entity in ipairs(entities) do
if entity.damage then
-- Safe to call living entity methods
entity:damage(5.0)
entity:push(0, 0.5, 0)
end
end

15. 排程回呼中缺少只有事件才有的資料​

症狀: 在 scheduler:run_later() 或 scheduler:run_repeating() 的回呼內部,來自點擊/戰鬥事件的資料不見了。

原因: 排程回呼收到新的 context,不保留原始 Bukkit 事件或一般區域事件的玩家,context.event 永遠為 nil。道具腳本僅在點擊、區域進出等玩家觸發的鉤子中收到頂層 context.player。之後需要該玩家時,應在原始鉤子內從 context.player 或 context.event.player 保存參照。

解決方法: 存取世界與狀態時使用回呼的新 context。需要原始事件中的玩家或實體時,在排程前保存它,並於執行時確認仍然有效。

on_right_click = function(context)
local clicked_player = context.player or (context.event and context.event.player)

context.scheduler:run_later(20, function(later_context)
-- Prop scripts should use a captured player when they need the clicker.
local player = later_context.player or clicked_player
if player and player.is_valid then
player:send_message("&aDelayed message!")
end
end)
end

延遲配置生成的工作原理​

了解延遲配置系統有助於避免設定新道具時的困惑:

  1. 首次生成: 當道具生成且不存在相鄰的 .yml 檔案時,FMM 非同步建立配置。這意味著檔案在背景執行緒中寫入,不會立即可用。

  2. 預設值: 生成的配置包含 isEnabled: true 和空的 scripts: [] 列表。

  3. 首次生成時沒有腳本: 由於配置是在道具已經生成之後建立的(且沒有列出腳本),道具在首次生成時不會關聯任何腳本。

  4. 編輯、重新載入、重新生成: 加入腳本名後,成功完成 /fmm reload。道具設定、放置檢查及選單腳本清單共用已準備好的目錄。新道具使用該目錄,不會重讀編輯後的 YAML。修改 Lua 也需要重新載入。

  5. 配置位置: .yml 檔案建立在與模型檔案相同的目錄中,具有相同的基本名稱。例如:

    • 模型:plugins/FreeMinecraftModels/models/fountain.fmmodel
    • 配置:plugins/FreeMinecraftModels/models/fountain.yml
快速設定流程
  1. 將模型放入 models/
  2. 將腳本放入 scripts/
  3. 生成道具一次(生成 .yml)
  4. 編輯 .yml 新增 scripts: [my_script.lua]
  5. 重新載入以探索 Lua 檔案,再放置新道具並確認你新增的啟動日誌

閱讀錯誤訊息​

鉤子和回呼錯誤會產生 [Lua] 區塊,指出檔案與執行環境。例外包含可辨識的程式碼位置時才會顯示行號。探索檔案與配置錯誤可能使用不同的警告。

典型的錯誤如下所示:

[Lua] Error in 'my_door.lua' at line 12 during 'on_right_click':
[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函式接收了錯誤類型的引數。訊息顯示了期望的類型與實際類型。
執行預算超限Lua instruction budget exceeded (250000 instruction limit) 或 Lua CPU-time budget exceeded (50ms current-thread CPU limit):呼叫遭中止,腳本實例被停用。
提示

在深入程式碼之前,請始終閱讀完整的 [Lua] 錯誤訊息。它通常會直接指向解決方法。


不要假設存在未文件化的方法​

FMM Lua API 公開了一組特定的方法。不要假設存在簡寫名稱或替代名稱。常見錯誤:

  • context.prop:get_location() -- 不存在。使用 context.prop.current_location(欄位,不是方法)。
  • context.prop:set_animation("open") -- 不存在。使用 context.prop:play_animation("open", true, true)。
  • context.event:setCancelled(true) -- 不存在。使用 context.event.cancel()。
  • context.cooldowns:check_global(...) -- 不存在。全域冷卻使用 context.cooldowns:global_ready() 和 context.cooldowns:set_global(ticks)。
  • context.player -- 僅在點擊、監控區域進出,以及由玩家射出投射物時的 on_projectile_hit 等涉及玩家的 FMM 道具鉤子內設定。它不是一般的道具擁有者欄位,在生命週期鉤子與排程回呼中為 nil。
  • context.boss -- 不存在。那是 EliteMobs 的。在 FMM 中使用 context.prop。

請在道具 API及其連結的共用參考中確認支援的方法。看似合理的名稱不能證明方法存在。


除錯技巧​

1. 大量使用 context.log:info()​

除錯時在每個步驟新增日誌訊息:

on_right_click = function(context)
context.log:info("Right click received!")
context.log:info("Event present: " .. tostring(context.event ~= nil))
context.log:info("State is_open: " .. tostring(context.state.is_open))
end

2. 在啟動時檢查主控台​

在 on_spawn 中加入 context.log:info("Prop script started"),重新載入後放置新道具。在主控台尋找這條日誌及腳本或配置錯誤。FMM 不會為每次關聯輸出成功訊息。

3. 驗證模型檔案路徑​

.yml 配置必須是模型檔案的同層檔案(相同目錄、相同基本名稱)。透過檢查以下內容來驗證:

  • 模型檔案路徑:plugins/FreeMinecraftModels/models/my_model.fmmodel
  • 配置檔案路徑:plugins/FreeMinecraftModels/models/my_model.yml

4. 單獨測試鉤子​

建構複雜腳本時,先單獨測試每個鉤子。一次新增一個鉤子,驗證其觸發後再新增下一個。

5. 檢查鉤子名稱的拼寫錯誤​

鉤子名拼寫錯誤是常見原因。未知鉤子會讓整個腳本在驗證時遭拒;請查看主控台錯誤,不要期待該檔案的其他鉤子仍然執行。


初學者學習路徑​

如果你想從零開始學習此系統,以下學習路徑效果很好:

  1. 撰寫一個只包含 api_version = 1 和記錄訊息的 on_spawn 的檔案。
  2. 將腳本新增到道具配置中,驗證日誌訊息是否出現。
  3. 新增 on_right_click 並在道具被點擊時記錄日誌。
  4. 新增含 context.event.cancel() 的 on_left_click,取消預設點擊傷害。
  5. 在右鍵點擊時播放動畫。
  6. 在右鍵點擊時新增聲音。
  7. 新增狀態和切換行為。
  8. 新增區域監視以實現接近偵測。
  9. 此後才開始撰寫帶有排程器的複雜多鉤子腳本。

每個步驟都建立在前一步的基礎上,你可以在每個階段進行測試。


下一步​