メインコンテンツまでスキップ

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、クールダウンのフィールドをそのパワーに適用します。別々のフォルダにある2つのパワーファイルが同じ名前を持つ場合、読み込まれるのは1つだけで、コンソールにスキップされたコピーが表示されます。

ファイルは api_version = 1 を含むテーブルを返す必要があります。そのテーブルに含められるのは api_version、省略可能な priority、対応するフック名だけで、フックの値は関数でなければなりません。補助関数はローカルに定義し、返すテーブルに任意のキーを追加しないでください。トップレベルのコードは定義の検証時とランタイム初期化時に実行され、その時点ではフックのコンテキストはありません。

2. フックが発火しない​

フック名がフックリストに記載されている通りに正確に綴られていることを確認してください。よくある間違い:on_boss_hit(誤り)vs. on_boss_damaged_by_player(正しい)、またはon_tick(誤り)vs. on_game_tick(正しい)。

返すテーブルに未対応のキーがあると、定義全体を読み込めません。読み込まれるのに実行されない場合は、ボスがそのパワーのファイル名を参照しているか、対象のイベントが発生しているか、ランタイムの失敗や一時停止、パワーの抑制がないかも確認してください。

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. タイムアウト / 実行予算超過​

各フック、予約されたコールバック、ファイル評価には、ネストした呼び出しと共有する上限として Lua 命令250,000回とスレッド CPU 時間50 msが適用されます。JVM が CPU 時間を測定できない場合は、代わりに経過時間250 msを使用します。エラーには超過した上限が表示されます。

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 msの実時間制限が適用されるわけでもありません。多数のエンティティの走査、coverage を指定しない大きなゾーンでの粒子生成、毎ティックの重い処理がよくある原因です。クールダウン、サンプル位置の削減、context.scheduler:run_every(...) を使い、処理を複数のティックに分散してください。

5. スケジューラコールバックが古いデータを使用している​

おそらくコールバックパラメータではなく外側のcontextを使用しています。function() ... context.boss ... endをfunction(tick_context) ... tick_context.boss ... endに変更してください。

予約されたコールバックには新しいコンテキストが渡され、元のイベント、直接ターゲット、プレイヤーは含まれません。必要なターゲットは予約前に明示的に保存し、使用時にまだ有効か確認してください。古いテーブルを保持するのではなく、現在位置など変化するプロパティを読み直してください。

6. ゾーンクエリがエンティティを返さない​

ゾーン定義を再確認してください。ネイティブゾーンの場合、kindが小文字であることを確認してください("sphere"であって"SPHERE"ではない)。スクリプトユーティリティの場合、shapeが大文字であることを確認してください("CONE"であって"cone"ではない)。また、originまたはTargetが実際に有効な位置に解決されることも確認してください。

エンティティフィルターとワールドも確認してください。ボスのネイティブゾーンクエリは既定で living を使い、ボス自身を除外してボスのワールドを検索します。スクリプトユーティリティのゾーンは既定で PLAYER を使います。Luaのゾーンターゲットはゾーンハンドルの full_target() または border_target() から取得してください。単独の context.script:target({targetType = "ZONE_FULL"}) にはゾーンがありません。

7. パーティクルが表示されない​

有効な Bukkit Particle 名を使ってください。"FLAME" と "flame" はどちらも大文字に正規化されます。位置、数、必要な追加データも確認してください。BLOCK にはこのワールドヘルパーが提供しないブロックデータが必要です。対応する別の粒子か、素材オプションを持つスクリプト用パーティクル API を使ってください。

8. クールダウンが機能していないように見える​

check_local(key, duration) は1回でクールダウンを確認し、設定します。local_ready(key) で確認して、攻撃開始時に set_local(duration, key) を呼ぶ方法も使えます。準備状態の確認だけではクールダウンは開始しません。明示的なキーは同じボスのパワー間で共有されるので、パワー固有の接頭辞を使ってください。YAMLメタデータはLuaフックの実行を自動的には制限しません。

9. ボスが死後もパワーを実行し続ける​

context.scheduler が所有するタスクは、パワーのランタイムが閉じるとキャンセルされます。ボスが除去される前に攻撃ループを止める必要があれば、on_exit_combat でキャンセルしてください。呼び出した補助関数も確認してください。Java側で独自のタスクを開始するものがあります。例えば world:run_fake_gold_nugget_damage(...) は独自のタイムアウト付きで毎ティック実行するループを開始するため、投射物の一組につき1回だけ呼んでください。


エラーメッセージの読み方​

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 nilnilであるものに対してフィールドにアクセスしようとしました。それより前のコードで初期化されていることを確認してください。
attempt to indexnilまたは無効な値のプロパティにアクセスしようとしました。
bad argument具体的な引数の不一致の詳細を表示します(期待される型 vs. 実際の型)。
バジェット超過命令数、CPU 時間、または代替の経過時間のどの上限を超えたかが表示されます。上記の説明を参照してください。
ヒント

失敗したスクリプトインスタンスは、そのエンティティに対して無効になります。ファイル修正後は定義を再読み込みし、新しいランタイムを作る必要があります。無効なインスタンスをそのまま残しても、失敗したフックを毎ティック再試行しません。


文書化されていないエイリアスが存在すると仮定しないでください​

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のボス専用エンティティラッパーでは entity:set_velocity_vector(vector) を使用してください。共有MagmaCoreラッパーには set_velocity(...) もあります。コンテキストが返すラッパーを確認してください。
  • entity:set_facing(...) -- entity:face_direction_or_location(direction_or_location)を使用してください。

使用するラッパーのAPIリファレンスを確認してください。ボスとエンティティ、ワールドと環境、ゾーンとターゲティング、共有APIに分かれています。似たコンテキストでも利用できるメソッドは異なります。もっともらしい別名を書いても利用できるとは限りません。


EliteScript作者向けの移行アドバイス​

すでに良いEliteScriptを書けるなら、Luaパワーを学ぶ最も簡単な方法は次のとおりです。

  1. イベント、ターゲット、ゾーン、相対ベクトル、パーティクルの観点で考え続けてください。 概念は同じです -- 構文だけが変わります。EliteScriptのイベントはon_spawnやon_boss_damaged_by_playerのようなフック名になります。ターゲットとゾーンは、EliteScriptゾーンとEliteScriptターゲットのページに文書化されている同じフィールド名を使い、テーブルとしてcontext.scriptに渡されます。

  2. 制御フローをLuaに移行してください。 ランダムロール、共有ヘルパー関数、ループ、永続ステート(context.state)、タスクスケジューリング(context.scheduler)は、純粋なEliteScriptでは簡単にできない、Luaが追加するものです。まずは1つの分岐や条件付きパワーをLuaに変換し、他はすべて同じに保つことから始めてください。

  3. ターゲット選択とゾーンの形状には context.script を使ってください。 仕様テーブルは targetType、shape、Target、Target2、FinalTarget、range、offset、relativeOffset、coverage、filter に実際のEliteScriptパーサーを使います。フィールド名や既定値はEliteScriptの文書で確認し、Luaハンドルの制限も読んでください。静的なLuaハンドルはYAMLアクションの track: false キャッシュを実行せず、継承されたアクションデータも持ちません。offset と relativeOffset は、LOCATION、LOCATIONS、サンプリングされたゾーン位置を含む、解決された位置に適用されます。範囲を固定する場合は固定した原点を明示してください。

  4. 2つのゾーンシステムに注意してください。 context.script:zone({shape = "SPHERE", ...}) は大文字の列挙値と Target 仕様テーブルを使うEliteScriptエンジンです。context.zones は小文字の kind と単純な origin/destination 位置を使います。キー形式を混ぜると、空または意図しないゾーン、警告、実行エラーにつながる場合があります。


初心者の学習パス​

このシステムをゼロから学びたい場合、次の段階を踏むとうまくいきます。

  1. api_version = 1とon_spawnだけのファイルを書く。
  2. ボスにメッセージを送信させるか、サウンドを再生させる。
  3. context.cooldownsでクールダウンを追加する。
  4. on_boss_damaged_by_playerのような、プレイヤーがトリガーするフックを1つ追加する。
  5. context.scheduler:run_after(...)で遅延アクションを1つ追加する。
  6. 単純なネイティブLuaゾーンクエリ、または単純なcontext.script:target(...)を1つ追加する。
  7. それからやっと、回転攻撃、ステートマシン、マルチステップのメカニクスに進む。

各ステップは前のステップの上に積み上がり、どの段階でもテストできます。最初のLuaパワーとしてマルチフェーズのボスを書こうとしないでください。


次のステップ​

  • はじめに -- ファイル構造、フック、最初のパワーの解説、コピー&ペースト用テンプレート
  • NPCスクリプト -- NPCの近接、インタラクション、ライフサイクルのスクリプト
  • 例とパターン -- 学習・応用できる完全な動作パワー