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

Luaスクリプティング:フックとライフサイクル

webapp_banner.jpg

このページでは、Luaパワーが定義できるすべてのフック、フックの実行順序、各ボスが独自の分離されたランタイムを取得する方法、およびサンドボックス内で利用可能な標準ライブラリ関数について説明します。

まだLuaパワーを書いたことがない場合は、まずはじめにから始めてください。

ボスパワーフック

このページでは、plugins/EliteMobs/powers/内のボスLuaパワーのフックについて説明します。NPC Luaスクリプトは独自のplugins/EliteMobs/npc_scripts/フォルダと、on_npc_interacton_npc_proximity_enterなどのNPC固有のフックを使用します。NPCスクリプトを参照してください。


フックリファレンス

すべてのLuaパワーファイルはテーブルを返します。そのテーブルの各キー(api_versionpriorityを除く)は、以下にリストされているフックのいずれかでなければなりません。ランタイムは、対応するゲームイベントが発生するたびに、一致する関数を呼び出します。

HookFires whencontext.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_damagedon_boss_damaged_by_playeron_boss_damaged_by_eliteon_player_damaged_by_bossに適用されます。

Field / MethodTypeDescription
event.damage_amountdouble生のダメージ値
event.damage_causestringSpigotのDamageCause名(例:"ENTITY_ATTACK""PROJECTILE"
event.damagerentity tableダメージを与えたエンティティ。エンティティによるダメージフックでのみ存在します。
event.projectileentity 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 / MethodTypeDescription
event.spawn_reasonstringSpigotのSpawnReason
event.cancel_event()スポーンをキャンセルします

死亡フック

on_deathに適用されます。

Field / MethodTypeDescription
event.entityentity table死亡しつつあるエンティティ

ゾーンフック

on_zone_enterおよびon_zone_leaveに適用されます。

Field / MethodTypeDescription
event.entityentity tableゾーンに入る、または出るエンティティ

Lua製のゾーンウォッチャーはcontext.eventを埋めません。入る/出るエンティティをコールバックに直接渡します。

キャンセル可能なイベント(一般)

基盤となるゲームイベントがキャンセル可能なフックはすべて、event.cancel_event()を公開します。特定のフックでcontext.eventnilの場合(例:on_game_tickon_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を通じて作成されたすべてのタスクは、それらを作成したランタイムによって所有されます。ボスがデスポーンすると:

  1. ランタイムがshutdown()を呼び出します。
  2. 所有されているすべてのタスク(ワンショット(run_after)と繰り返し(run_every)の両方)が自動的にキャンセルされます。
  3. すべてのゾーン監視がクリアされます。

ボスの削除時に、スケジュールされたタスクを手動でクリーンアップする必要は決してありません。ただし、不要な処理を避けるため、通常のゲームプレイ中に繰り返しタスクが不要になったときには、引き続きキャンセルすべきです:

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_spawnon_enter_combatに移してください。

Luaサンドボックス

Luaパワーはサンドボックス化されたLuaJ環境内で実行されます。ファイルシステムやJavaランタイムにアクセスできるいくつかのグローバルは削除されています。

削除されたグローバル

以下の標準Luaグローバルはnilに設定されており、使用できません:

RemovedWhy
debug内部VM状態を公開する
dofileファイルシステムアクセス
ioファイルシステムアクセス
load任意のコード読み込み
loadfileファイルシステムアクセス
luajava直接のJavaクラスアクセス
moduleモジュールシステム(不要)
osオペレーティングシステムアクセス
packageモジュールシステム(不要)
requireモジュールシステム / ファイルシステムアクセス

利用可能な標準ライブラリ

Lua標準ライブラリのその他すべては通常通り機能します:

CategoryFunctions
Mathmath.absmath.ceilmath.floormath.maxmath.minmath.randommath.sinmath.cosmath.sqrtmath.pi、およびその他すべてのmath.*関数
Stringstring.bytestring.charstring.findstring.formatstring.gsubstring.lenstring.lowerstring.matchstring.repstring.substring.upper、およびその他すべてのstring.*関数
Tabletable.inserttable.removetable.sorttable.concat、およびその他すべてのtable.*関数
Iteratorspairsipairsnext
Typetypetostringtonumberselectunpack
Error handlingpcallxpcallerrorassert
Otherprintrawgetrawsetrawequalrawlensetmetatablegetmetatable
ヒント

printはサーバーコンソールに書き込みますが、出力にはcontext.log:info(msg)またはcontext.log:warn(msg)を優先してください。これらにはパワー名が接頭辞として付くため、どのパワーがメッセージを生成したかを追跡しやすくなります。


emヘルパー名前空間

emテーブルはファイルロード時(どのフックが実行されるよりも前)に利用可能です。API全体で使用される位置テーブル、ベクトルテーブル、ゾーン定義を構築するためのヘルパーコンストラクターを提供します。

FunctionPurpose
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
}

ゾーンの形状、フィルター、ウォッチャー、ターゲティングパターンの完全な内訳については、ゾーンとターゲティングを参照してください。


次のステップ