Lua 腳本:疑難排解
本頁介紹撰寫或除錯 FreeMinecraftModels 道具腳本與物品腳本時可能遇到的常見問題,以及使用延遲配置生成系統的技巧。如果你正在尋找可運行的範例,請參閱範例與模式。如果你剛入門,請參閱入門指南。
FMM 使用 MagmaCore 的 ScriptInstance 執行環境。關於 FMM/MagmaCore 的沙盒、排程器、區域、world API、實體表格與玩家 UI 方法的說明文件,請參閱 MagmaCore Lua 腳本引擎頁面。EliteMobs 的 Boss 能力與 NPC 腳本重用同一個 Lua 沙盒,但提供各自的 context 表格。
常見問題
1. 配置檔未載入 / 腳本未關聯
症狀: 道具已生成,但不回應點擊或任何鉤子。
原因和解決方法:
-
尚不存在
.yml配置檔。 FMM 在道具首次生成時延遲建立配置。首次生成模型時,FMM 會非同步建立帶有預設值(已啟用、無腳本)的.yml配置。你需要編輯生成的配置以新增腳本檔名,然後重新生成道具。 -
配置中設定了
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副檔名。 -
檔案不存在。 請仔細檢查
.lua檔案是否確實存在於scripts/資料夾中。
3. 腳本完全無法載入
症狀: 沒有主控台錯誤,但沒有鉤子觸發。
檢查伺服器主控台在啟動時是否有 Lua 語法錯誤。最常見的原因是:
- 缺少
end來關閉函式或if區塊 - 括號不配對
- 回傳的表中鉤子定義之間缺少逗號
- 檔案不以
.lua結尾
如果存在 Lua 錯誤,主控台會輸出一個 [Lua] 錯誤區塊,包含檔名、行號和描述。
4. 鉤子從未觸發
症狀: 腳本已載入(你看到了 [FMM Scripts] Bound script 訊息),但特定的鉤子從未觸發。
驗證鉤子名稱是否與鉤子參考中列出的完全一致。常見錯誤:
| 錯誤名稱 | 正確名稱 |
|---|---|
on_click | on_right_click 或 on_left_click |
on_interact | on_right_click |
on_hit | on_left_click |
on_tick | on_game_tick |
on_remove | on_destroy |
on_arrow_hit | on_projectile_hit |
還要驗證道具是否確實有關聯的盔甲架實體。某些道具配置可能不會產生可點擊的實體。
5. 逾時 / 超出執行預算
症狀: 主控台顯示一則從 VM 內部拋出的 Lua 錯誤。超出預算的呼叫會在執行途中被中止,並顯示下列其中一則訊息:
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 毫秒的目前執行緒 CPU 時間,以先到者為準,而巢狀呼叫共用同一份額度。(第三則訊息只會出現在無法取得每執行緒 CPU 計時的 JVM 上;此時 MagmaCore 會改用較寬鬆的 250 毫秒經過時間限制。)這表示腳本在單次鉤子呼叫中執行了過多的工作。常見原因:
- 在
on_game_tick中遍歷了過多的實體 - 每 tick 建立了過多的粒子效果
- 在沒有將工作分散到多個 tick 的情況下執行了高開銷的迴圈
解決方法: 將繁重的工作放到 context.cooldowns 之後,或者使用 scheduler:run_repeating() 搭配合理的間隔來取代 on_game_tick。
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檔案以取得正確的動畫名稱。 -
模型沒有動畫。 並非所有模型都有動畫。驗證模型檔案是否確實包含動畫資料。
-
玩家不在資源包範圍內。 動畫在伺服器端執行,但玩家需要載入 FMM 資源包才能看到模型。
8. 粒子不出現
- 驗證粒子名稱是有效的 Bukkit
Particle列舉值且為大寫:"FLAME",而不是"flame"。 - 驗證位置位於已載入的區塊中。如果附近沒有玩家,區塊可能已卸載。
- 驗證
count至少為 1。 - 某些粒子(如
DUST)需要特定的額外資料,基本的spawn_particle()可能不支援。使用標準粒子如FLAME、HEART、HAPPY_VILLAGER、NOTE、ENCHANT等。
9. 聲音不播放
- 驗證聲音名稱是有效的 Bukkit
Sound列舉常數且為大寫。範例:"BLOCK_NOTE_BLOCK_HARP",而不是"block.note_block.harp"。 - 驗證位置座標是否正確(不全為零或 NaN)。
- 驗證音量大於 0。
10. 狀態意外重置
context.state 是按道具實例存在的,並在道具的生命週期內持續存在。如果狀態似乎被重置了:
- 道具可能已被移除並重新生成(每次生成都會建立新實例)。
- 你可能在排程器回呼中使用了錯誤的上下文變數來讀取狀態。始終使用回呼自身的上下文參數。
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. 物品腳本沒有啟動
症狀: 自訂物品已在玩家手上,但沒有任何物品鉤子觸發。
原因和解決方法:
-
YML 設定中沒有
material:欄位。 物品腳本只能用在設定中有material:的模型上。少了這個欄位,FMM 會把該模型當成道具,而不是自訂物品。 -
缺少
fmm_item_idPDC 鍵。 玩家物品欄中的物品必須帶有fmm_item_idPersistentDataContainer 鍵。透過原版指令或第三方外掛取得的物品可能沒有這個標籤。請使用/fmm giveitem <id>或管理員選單取得正確標記的物品。 -
腳本檔名沒有列在設定中。 請確認該物品的
.yml設定中,scripts:清單裡有這個腳本檔名,做法與道具相同。
13. 物品腳本啟動後立刻停止
症狀: 你在主控台中看到腳本快速地繫結又解除繫結,或是 on_equip 觸發後緊接著就觸發 on_unequip。
原因: 物品裝備追蹤是依據欄位變更事件觸發的。如果你用指令給自己一件物品而它進到了非啟用的欄位,或者給予過程中你的啟用欄位改變了,裝備/卸下循環就可能連續快速觸發。
解決方法: 用 /fmm giveitem <id> 給自己物品之後,請手動切換到裝有該物品的物品欄欄位。腳本是依據目前選取的是哪個欄位來啟動的,而不是只看物品有沒有在物品欄裡。
14. 鉤子有觸發,但腳本在呼叫實體方法時崩潰
症狀: 像 on_shift_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。如果你需要原始事件中確切的那位玩家或實體,請在排程之前先捕捉起來,並在使用時確認它仍然有效。
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)
-- Item scripts can use later_context.player here.
-- 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
延遲配置生成的工作原理
了解延遲配置系統有助於避免設定新道具時的困惑:
-
首次生成: 當道具生成且不存在相鄰的
.yml檔案時,FMM 非同步建立配置。這意味著檔案在背景執行緒中寫入,不會立即可用。 -
預設值: 生成的配置包含
isEnabled: true和空的scripts: []列表。 -
首次生成時沒有腳本: 由於配置是在道具已經生成之後建立的(且沒有列出腳本),道具在首次生成時不會關聯任何腳本。
-
編輯並重新生成: FMM 建立配置後,你編輯它以新增腳本檔名。下次道具生成時,腳本將被載入。
-
配置位置:
.yml檔案建立在與模型檔案相同的目錄中,具有相同的基本名稱。例如:- 模型:
plugins/FreeMinecraftModels/models/fountain.fmmodel - 配置:
plugins/FreeMinecraftModels/models/fountain.yml
- 模型:
- 將模型放入
models/ - 將腳本放入
scripts/ - 生成道具一次(生成
.yml) - 編輯
.yml新增scripts: [my_script.lua] - 重新生成道具——腳本現在已啟用
閱讀錯誤訊息
當 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 | 函式接收了錯誤類型的引數。訊息顯示了期望的類型與實際類型。 |
| Timeout | 你的腳本在 'hook_name' 中耗時 Xms(限制:50ms)——腳本已被停用以防止延遲。 |
在深入程式碼之前,請始終閱讀完整的 [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_local(...)-- 在 FMM 中不存在。使用context.state搭配scheduler:run_later()實現冷卻。context.player-- 在 FMM 道具腳本中不存在。使用context.world:get_nearby_players()尋找道具附近的玩家。context.boss-- 不存在。那是 EliteMobs 的。在 FMM 中使用context.prop。
如有疑問,請查看 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. 在啟動時檢查主控台
FMM 會為每次成功繫結記錄 [FMM Scripts] Bound script 'X' to prop 'Y'。如果你沒有看到此訊息,則配置或腳本未被載入。
3. 驗證模型檔案路徑
.yml 配置必須是模型檔案的同層檔案(相同目錄、相同基本名稱)。透過檢查以下內容來驗證:
- 模型檔案路徑:
plugins/FreeMinecraftModels/models/my_model.fmmodel - 配置檔案路徑:
plugins/FreeMinecraftModels/models/my_model.yml
4. 單獨測試鉤子
建構複雜腳本時,先單獨測試每個鉤子。一次新增一個鉤子,驗證其觸發後再新增下一個。
5. 檢查鉤子名稱的拼寫錯誤
鉤子不觸發的最常見原因是鉤子名稱拼寫錯誤。腳本會無錯載入,但拼寫錯誤的鉤子函式會在驗證時被拒絕。
初學者學習路徑
如果你想從零開始學習此系統,以下學習路徑效果很好:
- 撰寫一個只包含
api_version = 1和記錄訊息的on_spawn的檔案。 - 將腳本新增到道具配置中,驗證日誌訊息是否出現。
- 新增
on_right_click並在道具被點擊時記錄日誌。 - 新增
on_left_click搭配context.event.cancel()實現無敵。 - 在右鍵點擊時播放動畫。
- 在右鍵點擊時新增聲音。
- 新增狀態和切換行為。
- 新增區域監視以實現接近偵測。
- 此後才開始撰寫帶有排程器的複雜多鉤子腳本。
每個步驟都建立在前一步的基礎上,你可以在每個階段進行測試。