Luaスクリプティング:フックとライフサイクル
このページでは、Luaパワーが定義できるすべてのフック、フックの実行順序、各ボスが独自の分離されたランタイムを取得する方法、およびサンドボックス内で利用可能な標準ライブラリ関数について説明します。
まだLuaパワーを書いたことがない場合は、まずはじめにから始めてください。
このページでは、plugins/EliteMobs/powers/内のボスLuaパワーのフックについて説明します。NPC Luaスクリプトは独自のplugins/EliteMobs/npc_scripts/フォルダと、on_npc_interactやon_npc_proximity_enterなどのNPC固有のフックを使用します。NPCスクリプトを参照してください。
フックリファレンス
すべてのLuaパワーファイルはテーブルを返します。そのテーブルの各キー(api_versionとpriorityを除く)は、以下にリストされているフックのいずれかでなければなりません。ランタイムは、対応するゲームイベントが発生するたびに、一致する関数を呼び出します。
| Hook | Fires when | context.player available? |
|---|---|---|
on_spawn | エリートMobがスポーンしたとき | いいえ |
on_game_tick | ランタイムクロックがアクティブな間、サーバーティックごと(50ミリ秒ごと)に1回 | いいえ |
on_boss_damaged | ボスが任意のソースからダメージを受けたとき | いいえ |
on_boss_damaged_by_player | ボスがプレイヤーからダメージを受けたとき | はい |
on_boss_damaged_by_elite | ボスが別のエリートMobからダメージを受けたとき | いいえ |
on_player_damaged_by_boss | プレイヤーがこのボスからダメージを受けたとき | はい |
on_enter_combat | ボスが戦闘に入ったとき | はい |
on_exit_combat | ボスが戦闘を離れたとき | いいえ |
on_heal | ボスが回復したとき | いいえ |
on_boss_target_changed | ボスがターゲットを切り替えたとき | はい |
on_death | ボスが死亡したとき | いいえ |
on_phase_switch | フェーズボスが新しいフェーズに切り替わったとき | いいえ |
on_zone_enter | エンティティが監視中のゾーンに入ったとき | はい(エンティティがプレイヤーの場合) |
on_zone_leave | エンティティが監視中のゾーンから出たとき | はい(エンティティがプレイヤーの場合) |
context.playerが「いいえ」とリストされている場合、それにアクセスするとnilが返されます。使用する前に必ずnilチェックを行ってください。
トップレベルのon_zone_enterおよびon_zone_leaveフックは、EliteScript/ScriptZoneイベントによって発生します。context.zones:watch_zone(...)やcontext.script:zone(...):watch(...)から作成されたLua製のウォッチャーは、これらのトップレベルのフックを呼び出す代わりに、自身のon_enter / on_leaveコールバックを直接呼び出します。
典型的なマルチフックパワー
1つのLuaパワーは、必要なだけ多くのフックを定義できます。以下は、3つのフックを一緒に使用するスケルトンです:
return {
api_version = 1,
on_enter_combat = function(context)
-- Initialize per-fight state when combat begins
context.state.hit_count = 0
context.log:info("Combat started!")
end,
on_boss_damaged_by_player = function(context)
-- Track hits and trigger an ability every 5th hit
context.state.hit_count = (context.state.hit_count or 0) + 1
if context.state.hit_count % 5 ~= 0 then
return
end
if not context.cooldowns:check_local("counter_attack", 100) then
return
end
-- Fire a projectile back at the player
local origin = context.boss:get_location()
origin:add(0, 1, 0)
context.boss:summon_projectile(
"SMALL_FIREBALL", origin, context.player:get_location(), 1.5
)
end,
on_death = function(context)
-- Spawn a firework on death
context.world:spawn_particle_at_location(
context.boss:get_location(), "EXPLOSION_EMITTER", 1
)
end
}
イベントデータ(context.event)
一部のフックは、フックをトリガーしたゲームイベントに関するデータを公開するcontext.eventテーブルを受け取ります。利用可能なフィールドは、どのフックが実行されているかによって異なります。
ダメージフック
on_boss_damaged、on_boss_damaged_by_player、on_boss_damaged_by_elite、on_player_damaged_by_bossに適用されます。
| Field / Method | Type | Description |
|---|---|---|
event.damage_amount | double | 生のダメージ値 |
event.damage_cause | string | SpigotのDamageCause名(例:"ENTITY_ATTACK"、"PROJECTILE") |
event.damager | entity table | ダメージを与えたエンティティ。エンティティによるダメージフックでのみ存在します。 |
event.projectile | entity table | ダメージを与えたものが発射物だった場合の発射物エンティティ。 |
event.set_damage_amount(n) | — | ダメージを固定値で上書きします |
event.multiply_damage_amount(n) | — | 現在のダメージをn倍します |
event.cancel_event() | — | ダメージイベントを完全にキャンセルします |
on_boss_damaged_by_player = function(context)
-- Halve all projectile damage
if context.event.damage_cause == "PROJECTILE" then
context.event.multiply_damage_amount(0.5)
end
end
スポーンフック
on_spawnに適用されます。
| Field / Method | Type | Description |
|---|---|---|
event.spawn_reason | string | SpigotのSpawnReason名 |
event.cancel_event() | — | スポーンをキャンセルします |
死亡フック
on_deathに適用されます。
| Field / Method | Type | Description |
|---|---|---|
event.entity | entity table | 死亡しつつあるエンティティ |
ゾーンフック
on_zone_enterおよびon_zone_leaveに適用されます。
| Field / Method | Type | Description |
|---|---|---|
event.entity | entity table | ゾーンに入る、または出るエンティティ |
Lua製のゾーンウォッチャーはcontext.eventを埋めません。入る/出るエンティティをコールバックに直接渡します。
キャンセル可能なイベント(一般)
基盤となるゲームイベントがキャンセル可能なフックはすべて、event.cancel_event()を公開します。特定のフックでcontext.eventがnilの場合(例:on_game_tick、on_heal)、操作する基盤となるイベントはありません。
エンティティテーブルのフィールドの全容については、ボスとエンティティを参照してください。ダメージ原因とスポーン理由の値については、列挙型と値を参照してください。
フックの実行順序
ボスに複数のLuaパワーがアタッチされている場合、各パワーのフックが同じイベントに対して呼び出されます。順序はpriorityフィールドによって決まります:
- 値が低いものが先に実行されます(デフォルトは
0)。 - 同じ優先度のパワーはロード順で実行されます(実質的に未指定)。
return {
api_version = 1,
priority = -10, -- runs before most other powers
on_boss_damaged_by_player = function(context)
-- This runs early, so other powers see any state changes we make
context.state.last_attacker = context.player.uuid
end
}
優先度は、同じボス上のLuaパワー間の順序にのみ影響します。EliteScriptの実行順序とは相互作用しません。
ランタイムモデル
ボスごとに1つのランタイム
すべてのボスエンティティは、独自の独立したLuaランタイムインスタンスを取得します。ボスがスポーンすると、EliteMobsはLuaソースをロードし、新しいサンドボックス環境で評価し、返されたテーブルを保存します。ボスがデスポーンまたは削除されると、ランタイムはシャットダウンされます。
これは次のことを意味します:
- ファイル評価中に設定されたグローバルLua変数(
local functionを使ったヘルパー関数など)は、そのボスにプライベートです。 - 返されたテーブルのフック関数は、ボス間で共有されることは決してありません。
状態の分離
各ランタイムには独自のcontext.stateテーブルがあります。1つのボスの状態は、たとえ同じLuaパワーファイルを共有していても、他のすべてのボスからは完全に見えません。フックをまたいで必要なカウンター、フラグ、タイマー、その他のボスごとのデータを保存するには、context.stateを使用します。
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
-- Each boss tracks its own enrage counter independently
context.state.enrage_hits = (context.state.enrage_hits or 0) + 1
if context.state.enrage_hits >= 20 then
context.boss:apply_potion_effect("SPEED", 200, 2)
end
end
}
スケジュールされたタスクの所有権
context.schedulerを通じて作成されたすべてのタスクは、それらを作成したランタイムによって所有されます。ボスがデスポーンすると:
- ランタイムが
shutdown()を呼び出します。 - 所有されているすべてのタスク(ワンショット(
run_after)と繰り返し(run_every)の両方)が自動的にキャンセルされます。 - すべてのゾーン監視がクリアされます。
ボスの削除時に、スケジュールされたタスクを手動でクリーンアップする必要は決してありません。ただし、不要な処理を避けるため、通常のゲームプレイ中に繰り返しタスクが不要になったときには、引き続きキャンセルすべきです:
return {
api_version = 1,
on_enter_combat = function(context)
local pulse_count = 0
local task_id
task_id = context.scheduler:run_every(20, function(tick_context)
pulse_count = pulse_count + 1
if pulse_count > 10 or not tick_context.boss.exists then
tick_context.scheduler:cancel_task(task_id)
return
end
tick_context.world:spawn_particle_at_location(
tick_context.boss:get_location(),
{ particle = "FLAME", amount = 20, speed = 0.1 }
)
end)
end
}
ティックごとのクロックの動作
Luaパワーインスタンスの内部ティッククロックは、パワーがon_game_tickフックを定義している場合にのみ実行されます。context.zones:watch_zone(...)またはcontext.script:zone(...):watch(...)を通じて作成されたゾーン監視は、パワーのトップレベルのon_game_tickフックを有効にする代わりに、独自の所有する繰り返しタスクを作成します。
パワーにon_game_tickもゾーンウォッチャーもない場合、ティックごとの処理は発生しません。ティック処理とゾーンウォッチャーのタスクは、ボスがデスポーンするかランタイムがシャットダウンすると自動的にキャンセルされます。
エラーとパフォーマンスの動作
EliteMobsはLuaパワーに対して厳格なエラーおよびパフォーマンス制限を課します:
例外
フック関数またはスケジュールされたコールバックがLuaエラーをスローした場合(またはAPI呼び出しからJava例外が表面化した場合)、そのボスインスタンスに対してパワーは即座に無効化されます。ランタイムはシャットダウンされ、所有されているすべてのタスクがキャンセルされます。
エラーは、パワーのファイル名、行番号、実行中だったフックとともにサーバーコンソールに記録されます:
[Lua] Error in 'frost_cone.lua' at line 35 during 'on_boss_damaged_by_player':
[Lua] -> ...explanation of what went wrong...
[Lua] -> Script has been disabled for this entity to prevent further errors.
実行バジェット
すべてのフック呼び出しとすべてのコールバック呼び出しは時間が計測されます。1回の呼び出しに50ミリ秒を超える時間がかかると、コンソール警告とともにパワーが無効化されます:
[Lua] my_power.lua took 73ms in 'on_game_tick' (limit: 50ms) — script disabled to prevent lag.
これにより、暴走したスクリプトがサーバーをフリーズさせるのを防ぎます。バジェット内に収めるには:
- フック内で境界のないループを避けてください。
context.scheduler:run_every(...)を使用して、処理を複数のティックに分散させてください。 on_game_tickハンドラは軽量に保ってください。これらは毎ティック実行されます。- 重い初期化処理は毎ティック繰り返すのではなく、
on_spawnやon_enter_combatに移してください。
Luaサンドボックス
Luaパワーはサンドボックス化されたLuaJ環境内で実行されます。ファイルシステムやJavaランタイムにアクセスできるいくつかのグローバルは削除されています。
削除されたグローバル
以下の標準Luaグローバルはnilに設定されており、使用できません:
| Removed | Why |
|---|---|
debug | 内部VM状態を公開する |
dofile | ファイルシステムアクセス |
io | ファイルシステムアクセス |
load | 任意のコード読み込み |
loadfile | ファイルシステムアクセス |
luajava | 直接のJavaクラスアクセス |
module | モジュールシステム(不要) |
os | オペレーティングシステムアクセス |
package | モジュールシステム(不要) |
require | モジュールシステム / ファイルシステムアクセス |
利用可能な標準ライブラリ
Lua標準ライブラリのその他すべては通常通り機能します:
| Category | Functions |
|---|---|
| Math | math.abs、math.ceil、math.floor、math.max、math.min、math.random、math.sin、math.cos、math.sqrt、math.pi、およびその他すべてのmath.*関数 |
| String | string.byte、string.char、string.find、string.format、string.gsub、string.len、string.lower、string.match、string.rep、string.sub、string.upper、およびその他すべてのstring.*関数 |
| Table | table.insert、table.remove、table.sort、table.concat、およびその他すべてのtable.*関数 |
| Iterators | pairs、ipairs、next |
| Type | type、tostring、tonumber、select、unpack |
| Error handling | pcall、xpcall、error、assert |
| Other | print、rawget、rawset、rawequal、rawlen、setmetatable、getmetatable |
printはサーバーコンソールに書き込みますが、出力にはcontext.log:info(msg)またはcontext.log:warn(msg)を優先してください。これらにはパワー名が接頭辞として付くため、どのパワーがメッセージを生成したかを追跡しやすくなります。
emヘルパー名前空間
emテーブルはファイルロード時(どのフックが実行されるよりも前)に利用可能です。API全体で使用される位置テーブル、ベクトルテーブル、ゾーン定義を構築するためのヘルパーコンストラクターを提供します。
| Function | Purpose |
|---|---|
em.create_location(x, y, z [, world, yaw, pitch]) | オプションのワールド名、ヨー、ピッチを持つ位置テーブルを作成します |
em.create_vector(x, y, z) | ベクトルテーブルを作成します |
em.zone.create_sphere_zone(radius) | 球ゾーン定義を作成します |
em.zone.create_dome_zone(radius) | ドームゾーン定義を作成します |
em.zone.create_cylinder_zone(radius, height) | 円柱ゾーン定義を作成します |
em.zone.create_cuboid_zone(x, y, z) | 直方体ゾーン定義を作成します |
em.zone.create_cone_zone(length, radius) | 円錐ゾーン定義を作成します |
em.zone.create_static_ray_zone(length, thickness) | 静的レイゾーン定義を作成します |
em.zone.create_rotating_ray_zone(length, point_radius, animation_duration) | 回転レイゾーン定義を作成します |
em.zone.create_translating_ray_zone(length, point_radius, animation_duration) | 平行移動レイゾーン定義を作成します |
ゾーンビルダーは、:set_center(loc)(またはゾーンタイプに応じて:set_origin(loc) / :set_destination(loc))を持つチェーン可能なテーブルを返します。これらはファイルの先頭やフック内で使用するように設計されています:
-- At file scope: create a reusable zone shape
local blast_zone = em.zone.create_sphere_zone(5)
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
-- Anchor the zone to the boss's current location at call time
blast_zone:set_center(context.boss:get_location())
local entities = context.zones:get_entities_in_zone(blast_zone)
for i = 1, #entities do
if entities[i].type == "PLAYER" then
entities[i]:apply_potion_effect("SLOWNESS", 60, 1)
end
end
end
}
ゾーンの形状、フィルター、ウォッチャー、ターゲティングパターンの完全な内訳については、ゾーンとターゲティングを参照してください。
次のステップ
- ボスとエンティティ --
context.boss、context.player、エンティティラッパー - ワールドと環境 -- パーティクル、サウンド、スポーン、
context.world - ゾーンとターゲティング -- ネイティブゾーン、スクリプトユーティリティ、
context.zones/context.script - 例とパターン -- 学習して応用できる完全に動作するパワー
- 列挙型と値 -- すべての文字列定数のSpigot Javadocリンク
