Luaスクリプティング:トラブルシューティング
このページでは、FreeMinecraftModels のプロップスクリプトの作成やデバッグで起こりやすい問題と、遅延設定生成の仕組みを説明します。実例はサンプル&パターン、初めて使う場合ははじめにをご覧ください。
FMM は MagmaCore の ScriptInstance ランタイムを使用します。サンドボックス、スケジューラー、ゾーン、ワールド API、エンティティテーブル、プレイヤー UI はMagmaCore Lua スクリプティングエンジンを参照してください。EliteMobs のボスパワーと NPC スクリプトは Lua サンドボックスを再利用しますが、独自のコンテキストテーブルを公開します。
よくある問題
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. スクリプトがまったく読み込まれない
症状: コンソールにエラーは出ないが、フックが 1 つも発火しない。
起動時に Lua の構文エラーが出ていないかサーバーコンソールを確認してください。よくある原因は次のとおりです。
- 関数や
ifブロックを閉じるendが抜けている - 括弧が対応していない
- 返却するテーブル内で、フック定義の間のカンマが抜けている
- ファイルが
.luaで終わっていない
ファイル検出時の失敗は、無効・廃止済みスクリプトの警告や読み込みエラーとして表示されます。フックやコールバックの失敗はファイル名と実行コンテキストを含む [Lua] ブロックで表示され、例外から取得できる場合だけ行番号が付きます。
4. フックが一度も発火しない
症状: 自分で追加した起動ログは表示されるが、特定のフックが実行されない。
フック名がフックリファレンスと完全に一致するか確認してください。よくある間違い:
| 間違った名前 | 正しい名前 |
|---|---|
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() を使用してください。
命令数の上限は Lua のループを制限します。時間の確認は 1,024 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などの有効なキー、独自リソースパックのキーを使用できます。クライアントのリソースパックにそのサウンドが必要です。 - 位置の座標が正しいか(すべて 0 や NaN になっていないか)確認してください。
- 音量が 0 より大きいか確認してください。
10. 状態が予期せずリセットされる
context.state は 1 つのスクリプトインスタンスに属し、そのインスタンスが稼働している間保持されます。同じプロップの scripts: にある別々のスクリプト間では共有されません。リセットされたように見える場合は、次を確認してください。
- プロップが削除されて再スポーンされたか、チャンクがアンロードされてから再び読み込まれた可能性があります。スポーンやチャンクの読み込みのたびに新しいインスタンスが作られます。これを越えて残す必要がある値は
prop:set_persistent_dataで保存してください。 - 正常な再読み込みやスクリプトエラーによって以前のインスタンスが終了した可能性があります。コンソールを確認してください。ファイルを直すだけでは終了済みのインスタンスは再開せず、再読み込みと再紐付けが必要です。
- スケジューラーのコールバック内で、誤った context 変数を使って状態を読んでいる可能性があります。必ずコールバック自身の context パラメーターを使用してください。
11. os ライブラリが利用できない
症状: attempt to index nil (os) のようなエラーでスクリプトがクラッシュする。
os ライブラリは Lua サンドボックスから完全に削除されています。os.time()、os.clock() をはじめ、いかなる os の関数も使用できません。
対処: ワールド時間には context.world:get_time() を使うか、状態と on_game_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() のコールバック内で、クリック/戦闘イベントに由来するデータが失われている。
原因: コールバックは新しいコンテキストを受け取り、元の Bukkit イベントや汎用ゾーンの対象プレイヤーは保持しません。context.event は常に nil です。プロップの context.player はクリックやゾーンの出入りなど、プレイヤーによるフック内だけで設定されます。後で必要なら、元のフックで context.player または context.event.player を取得して保持してください。
対処: 通常のワールドや状態へのアクセスにはコールバックの新しいコンテキストを使います。元のイベントのプレイヤーやエンティティが必要なら、予定する前に取得し、実行時にも有効か確認してください。
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
遅延設定生成の仕組み
遅延設定システムを理解しておくと、新しいプロップをセットアップする際の混乱を避けられます。
-
初回スポーン: プロップがスポーンした時点で兄弟の
.ymlファイルが存在しない場合、FMM は設定を非同期に作成します。つまり、ファイルはバックグラウンドスレッドで書き込まれ、すぐには利用できません。 -
デフォルト値: 生成される設定は
isEnabled: trueと空のscripts: []リストを持ちます。 -
初回スポーンではスクリプトなし: 設定はプロップがすでにスポーンした後に(しかもスクリプトが記載されていない状態で)作成されるため、初回スポーン時のプロップにはスクリプトが紐付きません。
-
編集、再読み込み、再生成: スクリプト名を追加し、
/fmm reloadを正常に完了させます。プロップ設定、設置判定、メニューのスクリプト一覧は同じ読み込み済みカタログを使います。新しいプロップもそのカタログを使い、編集済みYAMLを読み直しません。Luaの編集にも再読み込みが必要です。 -
設定の場所:
.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_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. フックを個別にテストする
複雑なスクリプトを作る際は、まず各フックを単独でテストすることから始めましょう。フックを 1 つずつ追加し、発火することを確認してから次へ進んでください。
5. フック名のつづり間違いを確認する
フック名の誤りはよくある原因です。未知のフックがあると検証時にスクリプト全体が拒否されます。同じファイルの他のフックが動くと期待せず、コンソールのエラーを確認してください。
初心者向け学習パス
このシステムをゼロから学びたい場合は、次の順序がうまくいきます。
api_version = 1とメッセージをログ出力するon_spawnだけを書いたファイルを作る。- そのスクリプトをプロップの設定に追加し、ログメッセージが表示されることを確認する。
on_right_clickを追加し、プロップがクリックされたときにログを出す。context.event.cancel()を伴うon_left_clickを追加し、クリックによる通常ダメージを抑える。- 右クリックでアニメーションを再生する。
- 右クリックで音を追加する。
- 状態とトグル動作を追加する。
- 近接検出のためのゾーン監視を追加する。
- そのうえで初めて、スケジューラーを使う複数フックの複雑なスクリプトへ進む。
各ステップは前のステップの上に積み上がっており、どの段階でもテストできます。