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

Luaスクリプティング:はじめに

webapp_banner.jpg

学べること​

このページでは、EliteMobs用の最初のLuaパワーを書く方法を、空のファイルから動作するボス能力まで段階的に解説します。最後にはフック、コンテキスト、クールダウン、およびすべてのLuaパワーファイルの一般的な構造を理解できるようになります。

基本に慣れたら、以下の関連ページに進んでください:

まだ進化中です

EliteMobsの同梱パワーの多くはLuaを使用していますが、ヘルパー名や動作はリリース間で変わることがあります。カスタムパワーは本番で使う前にステージングサーバーで確認し、更新後にリファレンスを読み直してください。

EliteScriptとの関係

Luaは既存のeliteScript: YAMLシステムを置き換えません。また、EliteScriptは非推奨ではありません。

  • 既存のアクション、ターゲット、ゾーン、条件、クールダウン、相対ベクトルページを使った宣言的なYAMLベースのスクリプティングが必要な場合はEliteScriptを使用してください。
  • 変数、ループ、ランダム選択、再利用可能なヘルパー関数、ボスごとの永続状態、より伝統的なプログラミングフローが必要な場合はLuaパワーファイルを使用してください。

Script Utilities API(context.script)は仕様テーブルをEliteScriptのターゲット、ゾーン、相対ベクトル、パーティクルのクラスに渡します。各パーサーと制限は共通ですが、独立したLuaハンドルにはYAMLアクションのすべてのコンテキストがあるわけではありません。継承ターゲットや着地位置には、これらのハンドルにないコンテキストが必要です。LuaヘルパーはYAMLアクションの待機、条件、ターゲットキャッシュも自動適用しません。ゾーンとターゲティングのページで違いを説明しています。


Luaパワーとは​

Luaパワーは、EliteMobsの通常のpowersディレクトリツリーに配置される独立した.luaファイルで、通常のパワーファイルと全く同じように参照されます。

ボスパワーとNPCスクリプトの違い

このページで扱うのはボス用のLuaパワーです。NPCのLuaスクリプトは別の機能で、plugins/EliteMobs/npc_scripts/に配置し、NPC設定のscripts:リストを通じて割り当て、on_npc_proximity_enterのようなNPC専用のフックを使用します。NPCスクリプトを参照してください。

Luaパワーが得意なこと​

Luaパワーは以下のような場合に力を発揮します:

  • 攻撃ローテーションとランダムな技選択
  • context.stateを使ったフック間の永続状態
  • YAMLの待機で全てを構築せずに行う遅延・繰り返しアクション
  • 1ファイル内で共有されるカスタムヘルパー関数
  • 純粋なEliteScriptでは扱いにくい複雑な分岐
  • EliteScriptスタイルのターゲティングとゾーン定義を再利用したいボスロジック

パワーが主に「イベントをトリガーし、いくつかのスクリプトアクションを実行する」だけであれば、既存のEliteScriptドキュメントが最も速く明確な構築方法です。パワーに本格的なプログラムフローが必要な場合は、Luaがそのためのツールです。


このページの対象読者​

このページは3種類の読者向けに書かれています:

  • EliteScriptを既に知っていて、「本格的なプログラミング」を一度に全て学ぶことなくLuaを学びたい人
  • EliteMobsスクリプティングが初めてで、正確な名前の完全なリファレンスが必要な人
  • AIを使ってパワーを作成し、AIが偽の情報を生成した時にそれを見分けるための十分な詳細が必要な人

有用なパワーを書く前に、完全なLua開発者になる必要はありません。ほとんどの実用的なEliteMobsパワーに必要なのは:

  • 返されるテーブルに有効なフックを配置する方法
  • contextから値を読み取る方法
  • if ... then return endで早期終了する方法
  • いくつかのヘルパーメソッドを正確に呼び出す方法
  • 既存のEliteScriptドキュメントから適切なターゲットとゾーンの仕様をコピーする方法

メンタルモデル:EliteScript vs. Lua​

EliteScriptを知っている場合、この比較がLuaパワーを理解する最速の方法です:

EliteScriptの用語で考える場合Luaでは通常以下を意味する
Eventson_spawnやon_boss_damaged_by_playerのようなフック名
Cooldownscontext.cooldowns(下記のcontext.cooldownsを参照)
Actionscontext.world:spawn_particle_at_location(...)やcontext.script:damage(...)のような直接メソッド呼び出し
Targetscontext.script:target({...}) -- EliteScript Targetのフィールド名を使用
Zonescontext.script:zone({...})またはネイティブcontext.zonesヘルパー -- ゾーンとターゲティングを参照
Relative vectorscontext.script:relative_vector({...})またはcontext.vectors
Script flow自分自身のLuaのif文、ヘルパー関数、タイマー、状態

最大の違いは以下の通りです:

  • EliteScriptはYAMLで何が起こるべきかを記述します。
  • Luaはコードを使っていつ、なぜ、どの分岐が実行されるべきかを決定できます。

EliteScriptが「設定」のように感じるなら、Luaは「設定+意思決定」のように感じます。


Luaミニ入門​

多くのパワーで使うLuaの概念は、変数(local x = 5)、関数(function foo() end)、if条件(if x then ... end)、テーブル({key = value})、nil(値がないこと)です。セミコロンは任意です。テーブルには波括弧を使い、関数や制御ブロックはendで閉じます。

例を交えた完全な解説は、**MagmaCore Luaスクリプティングエンジン — Luaミニ入門**を参照してください。この入門はすべてのNightbreakプラグインで共通なので、一度学べばどこでも活かせます。


最初の動作するパワー、ステップバイステップ​

完全に初めての場合、これが「最初の成功」への最適な進行順です。

ステップ1の前:保存してボスに割り当てる​

Luaファイルを通常のEliteMobsパワーフォルダーに保存します。例:

plugins/
EliteMobs/
powers/
first_test.lua

そのファイル名を通常のpowers:リストを使ってボス設定に追加します:

powers:
- first_test.lua

初心者の完全なループは:

  1. plugins/EliteMobs/powers/にファイルを保存
  2. .luaファイル名をボスのpowers:リストに追加
  3. /em reloadで変更したファイルを読み込み、新しいテスト用ボスをスポーンする。リロードはプラグインと稼働中のコンテンツに影響するため、ステージングサーバーを使ってください。
  4. フックを発動させ、目に見える結果とコンソールを確認する。ファイルの保存や、リロードせずに別のボスをスポーンするだけでは、読み込み済みの定義は更新されません。

ボスファイル、パワーリスト、カスタムボスの構造についてより詳しい背景が必要な場合は、カスタムボスの作成を参照してください。そのページでは、powers:リストの動作を含め、ボスのセットアップを詳しく解説しています。

ステップ1:ファイルをロードさせる​

return {
api_version = 1,

on_spawn = function(context)
end
}

ローダーはLua構文、返されるテーブル、フック名を検証します。空のフックには目に見える結果がないため、何も起きないだけではファイルの検出や割り当てを証明できません。ステップ2のサウンドを追加し、新しいボスをスポーンして一連の動作を確認してください。

ステップ2:ボスに目に見えることをさせる​

return {
api_version = 1,

on_spawn = function(context)
context.boss:play_sound_at_self("entity.blaze.ambient", 1.0, 1.0)
end
}

これで最も重要な初心者の証明が得られます:フックが発動しています。

ステップ3:プレイヤーのヒットに反応する​

return {
api_version = 1,

on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end

context.player:send_message("&eYou hit the boss.")
end
}

これは3つの核心的な概念を教えます:

  • on_boss_damaged_by_playerがフック名
  • context.playerがそのフックに関わるプレイヤー
  • returnは必要なデータがない場合に早期終了する

ステップ4:クールダウンでスパムを防ぐ​

return {
api_version = 1,

on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end

if not context.cooldowns:local_ready("hello_message") then
return
end

context.player:send_message("&eYou woke up the boss.")
context.cooldowns:set_local(60, "hello_message")
end
}

ローカルクールダウンの1ティックは実際の経過時間50ミリ秒を表すため、サーバーのTPSが低下しても60は3秒です。この名前付きクールダウンは同じボスのパワー間で共有され、異なるボスとは共有されません。カスタムパワーを組み合わせる場合はキーにパワー名の接頭辞を付けてください。この例はローカルキーだけを確認し、ボスのグローバルクールダウンは確認しません。

ステップ5:本物のエフェクトを追加する​

return {
api_version = 1,

on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end

if not context.cooldowns:local_ready("shock") then
return
end

context.cooldowns:set_local(100, "shock")
context.player:send_message("&cStatic jumps from the boss into your armor!")
context.world:strike_lightning_at_location(context.player.current_location)
end
}

これは見た目だけの閃光ではなく、ワールドに実際の雷を発生させます。効果が引き起こすイベントでもクールダウンが見えるよう、効果より先に設定します。このローカルクールダウンの100ティックは実際の経過時間5秒です。


最初の実践的ワークフロー​

新しいLuaパワーを一から構築する際は、この順序を使ってください:

  1. ファイルを作成し、on_spawnを動作させる。
  2. 実際に使いたいフックに変更する。
  3. フックにcontext.playerやcontext.eventなど期待するデータがあることを確認する。
  4. ダメージやパーティクルの前に、まずメッセージやサウンドを追加する。
  5. クールダウンを追加する。
  6. ゲームプレイエフェクトを1つ追加する。
  7. その後に初めて、ヘルパー、状態、スケジューラーロジック、ゾーンを追加する。

この順序により、一度に1つのことだけが変わるため、デバッグが大幅に容易になります。


Luaファイルの配置場所​

.luaファイルを通常のパワー.ymlファイルと同じフォルダーツリーに配置します:

plugins/
EliteMobs/
powers/
mycoolpower.lua
attack_push.yml
subfolder/
myotherpower.lua

LuaパワーはEliteMobsが既にロードするパワーディレクトリから自動検出されます。

任意のYAMLメタデータ​

カスタムのmycoolpower.luaには、同じディレクトリに読み込み済みのmycoolpower.ymlまたはmycoolpower.yamlを添えられます。検出時にはそのYAMLのisEnabled、effect、powerType、powerCooldown、globalCooldownを読み取り、isEnabled: falseならそのカスタムLuaファイルを登録しません。対応するメタデータがない場合、エフェクトなし、パワータイプMISCELLANEOUSになります。攻撃の実行タイミングは引き続きLuaフックが制御し、クールダウンも自分で確認する必要があります。

同梱のLuaパワーの隣に読み込み済みの同じベース名のYAMLファイルがあると、同じフィールドがそのパワーにも適用されます。ない場合、同梱パワーは付属のメタデータを維持します。フィールドについてはパワーの作成を参照してください。

ボスがLuaパワーを参照する方法​

ボスファイルは引き続き通常のpowers:リストを使用します:

powers:
- attack_push.yml
- mycoolpower.lua

特別なフィールドは不要です。LuaパワーはeliteScript:を通してロードされません。

ファイル命名規則​

  • ボスはLuaパワーをファイル名で参照し、フォルダーパスではありません。
  • EliteMobsは現在、検出されたLuaパワーをベース名のみで登録します。
  • powers/fire.luaとpowers/bosses/fire.luaのような重複名は避けてください。EliteMobsはフルパスがアルファベット順で先になるファイルだけを読み込み、両方のファイルが同一でない限り、スキップしたコピーについて警告をログに出力します。

既製のLuaパワー​

同梱パワーの多くはLuaで書かれています。現在のソースには68個の具象Luaパワーが定義され、ファイルがない場合にplugins/EliteMobs/powers/へ配置されます。attack_fire.lua、frost_cone.lua、meteor_shower.lua、shield_wall.luaなどが含まれます。例として読めますが、編集済みのローカルコピーはプラグイン付属版と異なる場合があります。

既製ファイルは存在しない場合のみ書き込まれるため、編集内容は再起動後も残ります。アップデートで同梱パワーが変更されると、以前に同梱したバージョンの未編集のコピーは起動時にplugins/MagmaCore/outdated files/EliteMobs/へ移動され、代わりに現在のバージョンが書き込まれます。編集したコピーはそのまま残ります。同梱ファイルを削除すると、次のプラグイン初期化で付属版が再生成されます。置き換える前に、必要な独自の変更を保存してください。

ソース断片の共有​

ボスのLuaパワーは、単独行のコメント指示でヘルパーファイルを読み込めます。

-- @include helpers/common.inc

ローダーはパワーの検証前に参照先のテキストを挿入します。パスは指示を書いたファイルからの相対パスで、シンボリックリンク解決後も最上位のパワーファイルのディレクトリ内に収まる必要があります。循環するインクルード、16段階を超える入れ子、4 MiBを超えるファイル、展開後4,194,304文字を超えるソースは拒否されます。

断片には.incなど.lua以外の拡張子を使い、独立したパワーとしても検出されるのを避けてください。ヘルパー関数は最後のreturnテーブルより前に宣言します。この指示はボスパワーの読み込み専用で、Luaのrequireやファイルシステムアクセスを有効にするものではありません。NPCスクリプトでは処理されません。

レガシーの .yml 名も引き続き解決されます​

attack_fire.ymlを参照するボス設定は引き続き動作します。.ymlまたは.yamlの参照では、まず同じベース名の読み込み済みLuaパワーを探し、なければ指定名をそのまま使います。検証失敗や無効化されたファイルは読み込み済みの代替にはなりません。ベース名の綴りと大文字・小文字を揃えてください。同梱Luaパワーを変更するときは既存ファイルを編集してリロードします。別のディレクトリに同じベース名を作る方法では、確実に上書きできません。


最小ファイル契約​

すべてのLuaパワーはreturnでテーブルを返す必要があります。このテーブルは意図的に厳格です。

必須およびオプションのトップレベルフィールド​

フィールド必須型備考
api_versionはいNumber整数1を使用
priorityいいえNumber整数を使用。小さい値から実行。既定値は0。YAMLパワーの優先度も0
サポートされるフックキーいいえFunctionフックリファレンスに記載されている正確なフック名の1つを使用する必要がある

バリデーションルール​

  • ファイルはテーブルを返す必要があります。
  • api_versionは必須で、現在は1でなければなりません。
  • priorityは存在する場合、数値でなければなりません。
  • 追加のトップレベルキーはすべてサポートされるフック名でなければなりません。
  • すべてのフックキーは関数を指している必要があります。
  • 不明なトップレベルキーは拒否されます。

ヘルパー関数とローカル定数は最終的なreturnの上に配置し、実際のフックでない限り返されるテーブルの内部に置かないでください。

ファイル本体は検証時に評価され、その後、各ボスパワーの実行環境ごとに別のLua環境で再評価されます。contextはフックやコールバックの引数としてのみ存在し、トップレベルのグローバル変数ではありません。ローカル変数はその実行環境内で保持できますが、検証時の値は引き継がれません。実行環境ではファイル・OS・Javaアクセスとrequireが除去されるため、ソース断片にはインクルード指示を使います。フックやコールバックの未処理エラーはその実行環境を閉じ、所有するタスクを取り消します。次の被弾時にも再試行しません。ファイルを修正してリロードしてから試してください。


コピー&ペーストスターターテンプレート​

最小の有効なLuaパワー​

return {
api_version = 1,

on_spawn = function(context)
end
}
local ATTACK_COOLDOWN = "my_attack"

local function can_run_attack(context)
return context.cooldowns:local_ready(ATTACK_COOLDOWN)
and context.cooldowns:global_ready()
end

local function run_attack(context)
context.boss:play_sound_at_self("entity.blaze.shoot", 1.0, 1.0)
context.cooldowns:set_local(100, ATTACK_COOLDOWN)
context.cooldowns:set_global(20)
end

return {
api_version = 1,
priority = 0,

on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end

if not can_run_attack(context) then
return
end

run_attack(context)
end
}

大きなファイルのレイアウト​

local CONSTANT_NAME = "value"

local function helper_function(context)
end

local function another_helper(context, value)
end

return {
api_version = 1,
priority = 0,

on_spawn = function(context)
end,

on_enter_combat = function(context)
end,

on_boss_damaged_by_player = function(context)
end,

on_exit_combat = function(context)
end
}

フック​

フックは、返すテーブル内の特別な名前の関数です。EliteMobsは何かが起こったとき(ボスがスポーンする、ダメージを受ける、戦闘に入るなど)にそれらを呼び出します。上記のチュートリアルでon_spawnとon_boss_damaged_by_playerを既に見ました。

最も一般的な開始フックはon_spawn、on_boss_damaged_by_player、on_enter_combat、on_exit_combatです。すべてのフックの完全なリスト、各フックで利用可能なコンテキストキー、実行順序の仕組みについては、フックとライフサイクルを参照してください。


contextとは?​

すべてのフック関数はcontextと呼ばれる1つの引数を受け取ります。これは何かが起こるたびにEliteMobsがあなたに渡すツールボックスと考えてください。ボス、プレイヤー、ワールド、クールダウンなどとやり取りするために必要なすべてが含まれています。

on_boss_damaged_by_player = function(context)
-- context.boss = the boss that was hit
-- context.player = the player who hit it
-- context.world = tools for spawning particles, sounds, etc.
-- context.cooldowns = cooldown management
-- context.state = your own persistent storage
end

contextは自分で作成しません。EliteMobsがそれを作成してフックに渡します。そこから読み取り、メソッドを呼び出すだけです。

備考

各フックは新しいcontextを受け取ります。context.stateテーブルは、このボス上のこのパワーの実行環境に属し、後続のフックやコールバック用にデータを保存できます。他のパワーの状態は別です。実行環境を閉じたりリロードしたりすると状態は失われます。


主要なcontext API​

contextから使用する最も重要なAPIは以下の通りです:

  • context.state -- このボス上のこのパワーの実行環境で保持されるLuaテーブル。フック間のフェーズ番号、タスクID、フラグを保存します。実行環境の削除やリロードをまたいでは保存されません。

  • context.log -- log:info(msg)、log:warn(msg)、log:debug(msg)によるコンソールログ。複数のパワーの出力を区別する必要があれば、メッセージにパワー名を含めてください。

  • context.cooldowns -- ボスのパワー間で共有する名前付きクールダウンとグローバルクールダウン。明示したキーはボス全体の名前空間を共有するため、パワー名の接頭辞を付けてください。nilキーはこのLuaファイル名の既定クールダウンを選択します。cooldowns:check_local(key, ticks)はローカルクールダウンの確認と設定を一度に行いますが、グローバルの確認や設定は行いません。全APIはフックとライフサイクルを参照してください。

  • context.scheduler -- scheduler:run_after(ticks, callback)は1回のコールバックを遅延実行します。scheduler:run_every(ticks, callback)は初回の間隔待機なしで繰り返します。初回の遅延を指定するにはscheduler:run_repeating(delay, interval, callback)を使います。コールバックは発端のイベントやプレイヤーを含まない新しいコンテキストを受け取ります。その引数を使い、必要なプレイヤーUUIDや位置は明示的に保存してください。遅延後もctx.playerがあるとは限りません。戦闘中だけの繰り返しはon_exit_combatで取り消してください。それ以外の所有タスクは、取り消すか実行環境が閉じるまで継続します。フックとライフサイクルを参照してください。

  • context.boss / context.player -- 現在のイベントに関わるボスとプレイヤー。すべてのフィールドとメソッドについてはボスとエンティティを参照してください。

  • context.world -- パーティクル、エンティティ、サウンド、雷、ブロックのスポーン。ワールドと環境を参照してください。

  • context.zones / context.script -- ゾーンジオメトリ、ターゲティング、ダメージ、パーティクル。ゾーンとターゲティングを参照してください。


メソッド構文::と.​

: と . の違いは MagmaCore Luaスクリプティングエンジンを参照してください。エンティティメソッドと共有サービスは両方の形式に対応しますが、context.event.set_damage_amount(value) と context.event.multiply_damage_amount(multiplier) にはドットが必要です。コロンを使うとイベントテーブルが第1引数として渡されます。context.event:transfer_damage(target, amount)はコロンに対応します。em.create_location(...)などのグローバルコンストラクターにはドットを使います。


次のステップ​

基本を理解したら、Luaスクリプティングドキュメントの残りを探索してください:

YAMLベースのスクリプティングについては、EliteScriptページが引き続き正式なリファレンスです: