Luaスクリプティング:トラブルシューティング
このページでは、FreeMinecraftModels のプロップスクリプトやアイテムスクリプトを書いたりデバッグしたりする際に遭遇しやすい問題と、遅延設定生成システムを扱うためのヒントを紹介します。動作するサンプルをお探しの場合はサンプル&パターンを、これから始める場合ははじめにをご覧ください。
FMM は、Nightbreak のプラグイン間で共有される MagmaCore の Lua スクリプティングエンジンを使用しています。サンドボックス、スケジューラー、ゾーン、ワールド API、エンティティテーブル、プレイヤー UI メソッドといった共通概念のドキュメントについては、MagmaCore Lua スクリプティングエンジンのページを参照してください。
よくある問題
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設定内の名前は、大文字小文字や.lua拡張子も含めて、scripts/フォルダー内のファイル名と完全に一致していなければなりません。 -
ファイルが存在しない。
.luaファイルが実際にscripts/フォルダーにあるかを再確認してください。
3. スクリプトがまったく読み込まれない
症状: コンソールにエラーは出ないが、フックが 1 つも発火しない。
起動時に Lua の構文エラーが出ていないかサーバーコンソールを確認してください。よくある原因は次のとおりです。
- 関数や
ifブロックを閉じるendが抜けている - 括弧が対応していない
- 返却するテーブル内で、フック定義の間のカンマが抜けている
- ファイルが
.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命令、または 現在のスレッドのCPU時間 50 ms のいずれか早く到達した方までで、ネストした呼び出しは1つの割り当てを共有します。(3つ目のメッセージは、スレッドごとのCPU時間計測が利用できないJVMでのみ表示されます。その場合、MagmaCoreは代わりにより寛容な250 msの経過時間の上限を使用します。)スクリプトが1回のフック呼び出しで処理をしすぎています。よくある原因:
on_game_tickで多すぎるエンティティを走査している- 1 ティックあたりに多すぎるパーティクルを生成している
- 処理を複数ティックに分散させずに重いループを回している
対処: 重い処理を context.cooldowns の後ろに移すか、on_game_tick の代わりに適切な間隔を指定した scheduler:run_repeating() を使用してください。
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"です。 - 位置の座標が正しいか(すべて 0 や NaN になっていないか)確認してください。
- 音量が 0 より大きいか確認してください。
10. 状態が予期せずリセットされる
context.state はプロップインスタンスごとに保持され、そのプロップの生存期間中は維持されます。状態がリセットされているように見える場合は、
- プロップが削除されて再スポーンされた可能性があります(スポーンのたびに新しいインスタンスが作られます)。
- スケジューラーのコールバック内で、誤った context 変数を使って状態を読んでいる可能性があります。必ずコールバック自身の context パラメーターを使用してください。
11. os ライブラリが利用できない
症状: attempt to index nil (os) のようなエラーでスクリプトがクラッシュする。
os ライブラリは Lua サンドボックスから完全に削除されています。os.time()、os.clock() をはじめ、いかなる os の関数も使用できません。
対処: ワールド時間には context.world:get_time() を使うか、状態フラグと on_game_tick のティックカウンターを使って経過時間を手動で追跡してください。クールダウンには context.state のフラグと scheduler:run_later() を使用します。
12. アイテムスクリプトが有効にならない
症状: カスタムアイテムをプレイヤーが手に持っているのに、アイテムのフックが 1 つも発火しない。
原因と対処:
-
YML 設定に
material:フィールドがない。 アイテムスクリプトは、設定にmaterial:が設定されているモデルでのみ機能します。このフィールドがない場合、FMM はそのモデルをカスタムアイテムではなくプロップとして扱います。 -
fmm_item_idの PDC キーが欠けている。 プレイヤーのインベントリ内のアイテムにはfmm_item_idの PersistentDataContainer キーが必要です。バニラのコマンドやサードパーティ製プラグインで入手したアイテムには、このタグが欠けている場合があります。/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.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)
-- 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 | 関数が誤った型の引数を受け取りました。メッセージには期待される型と実際の型が表示されます。 |
| バジェット超過 | 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_local(...)-- FMM には存在しません。クールダウンにはcontext.stateとscheduler:run_later()を使用してください。context.player-- FMM のプロップスクリプトには存在しません(プロップの近くにいるプレイヤーを探すにはcontext.world:get_nearby_players()を使用してください)。アイテムスクリプトには存在します。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. 起動時にコンソールを確認する
FMM はバインドに成功するたびに [FMM Scripts] Bound script 'X' to prop 'Y' をログ出力します。このメッセージが表示されない場合、設定またはスクリプトが読み込まれていません。
3. モデルファイルのパスを確認する
.yml 設定はモデルファイルの兄弟(同じディレクトリ、同じベース名)でなければなりません。次を確認してください。
- モデルファイルのパス:
plugins/FreeMinecraftModels/models/my_model.fmmodel - 設定ファイルのパス:
plugins/FreeMinecraftModels/models/my_model.yml
4. フックを個別にテストする
複雑なスクリプトを作る際は、まず各フックを単独でテストすることから始めましょう。フックを 1 つずつ追加し、発火することを確認してから次へ進んでください。
5. フック名のつづり間違いを確認する
フックが発火しない最も多い理由は、フック名のつづり間違いです。スクリプト自体はエラーなく読み込まれますが、つづりを間違えたフック関数は検証時に拒否されます。
初心者向け学習パス
このシステムをゼロから学びたい場合は、次の順序がうまくいきます。
api_version = 1とメッセージをログ出力するon_spawnだけを書いたファイルを作る。- そのスクリプトをプロップの設定に追加し、ログメッセージが表示されることを確認する。
on_right_clickを追加し、プロップがクリックされたときにログを出す。- 無敵化のため
context.event.cancel()を伴うon_left_clickを追加する。 - 右クリックでアニメーションを再生する。
- 右クリックで音を追加する。
- 状態とトグル動作を追加する。
- 近接検出のためのゾーン監視を追加する。
- そのうえで初めて、スケジューラーを使う複数フックの複雑なスクリプトへ進む。
各ステップは前のステップの上に積み上がっており、どの段階でもテストできます。
次のステップ
- はじめに -- ファイル構造、フック、最初のスクリプトのウォークスルー、コピー&ペースト用テンプレート
- プロップ&アイテムAPI -- 完全なAPIリファレンス
- サンプル&パターン -- 学習や応用に使える完全動作スクリプト