Luaスクリプティング:例とパターン
このページではEliteMobs Luaパワーの完全な例と解説を紹介します。各例を個別の.luaファイルとして保存し、テスト用ボスに追加してください。編集後は/em reloadを実行し、新しいボスをスポーンさせます。slam、roar、frenzy、transformなどのアニメーション名はボスのカスタムモデルに存在する必要があり、組み込みアニメーションではありません。カスタムモデルのないボスはこれらの呼び出しをスキップします。以下の時間は20 TPSを前提とします。
Luaパワーが初めての方は、はじめにから始めてください。NPCについてはNPCスクリプトを参照してください。ボスパワーAPIの詳細は、APIリファレンス、ボスとエンティティ、ワールドと環境、ゾーンとターゲティング、Enumにあります。
例:スクリプトユーティリティによるゾーンベースのターゲティング
この例で学べること: context.scriptを使用してLuaからEliteScriptスタイルのゾーンジオメトリを作成し、パーティクルを生成し、ゾーン内のエンティティにダメージを与える方法。
完全なパワーファイル(クリックで展開)
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
local cone = context.script:zone({
shape = "CONE",
Target = {
targetType = "SELF",
offset = "0,1,0"
},
Target2 = {
targetType = "NEARBY_PLAYERS",
range = 20
},
radius = 5
})
context.script:spawn_particles(
cone:full_target(0.4),
{
particle = "FLAME",
amount = 1,
speed = 0.05
}
)
context.script:damage(cone:full_target(), 1.0, 1.5)
end
}
解説
-
ゾーン作成 --
context.script:zone(...)はEliteScriptゾーンと同じフィールド名を使います。Targetはコーンの頂点をボスの1ブロック上に設定します。最初に解決されたTarget2の位置が底面の中心となり、コーンの長さが決まります。radius = 5は底面の半径です。NEARBY_PLAYERSは各軸に20ブロック広がる直方体を検索し、最も近いプレイヤーを保証しません。このダメージフックで攻撃者を狙う場合はDIRECT_TARGETを使います。プレイヤーが見つからなければコーンは作られません。両端は同じワールド内の異なる位置である必要があります。 -
パーティクル生成 --
cone:full_target(0.4)は呼び出しごとにゾーンの各点を40%の確率で選ぶターゲットハンドルを返します。正確に40%の個数を保証するものではなく、ダメージ対象も絞り込みません。パーティクル設定にはEliteScriptのparticle、amount、speedを使います。ヘルパーは選んだゾーン位置をX軸とZ軸に半ブロックずらすため、表示がエンティティの当たり判定境界と完全に一致するわけではありません。 -
ダメージ --
context.script:damage(cone:full_target(), 1.0, 1.5)はコーン内で解決されたプレイヤーに当たります。ゾーンの既定フィルターがPLAYERだからです。最初の数値(1.0)は送信する基本ダメージ、2番目(1.5)はプレイヤーに適用するEliteMobsのダメージ倍率です。最終ダメージは戦闘処理によって決まります。
この例は通知されたヒットごとに動作し、ローカル・グローバルクールダウンも、パーティクルとダメージの間の予告時間もありません。他の攻撃と組み合わせる場合は明示的なクールダウン確認を追加してください。静的ゾーンへの問い合わせは毎回ターゲットを解決し直します。LuaゾーンハンドルはYAMLアクションの条件やターゲットキャッシュを自動適用しません。
context.scriptに渡されるゾーンとターゲットのテーブルはEliteScriptフィールド名(targetType、shape、Target、Target2、range、offset、coverage)を使用します。完全なリストはEliteScriptゾーンとEliteScriptターゲットを参照してください。
例:ステート+スケジューラ攻撃ループ
この例で学べること: context.stateを使用したランタイムステートの追跡、context.schedulerによる繰り返しタスク、適切な戦闘開始/終了ライフサイクル。
完全なパワーファイル(クリックで展開)
local function pick_action(context)
local roll = math.random(1, 2)
if roll == 1 then
context.boss:play_model_animation("slam")
else
context.boss:play_model_animation("roar")
end
end
return {
api_version = 1,
on_spawn = function(context)
context.state.started = false
context.state.loop_task_id = nil
end,
on_enter_combat = function(context)
if context.state.started then
return
end
context.state.started = true
context.state.loop_task_id = context.scheduler:run_every(100, function(loop_context)
if loop_context.boss.exists then
pick_action(loop_context)
end
end)
end,
on_exit_combat = function(context)
if context.state.loop_task_id ~= nil then
context.scheduler:cancel_task(context.state.loop_task_id)
context.state.loop_task_id = nil
end
context.state.started = false
end
}
解説
- ステートの初期化 --
on_spawnでcontext.state.startedをfalse、context.state.loop_task_idをnilに設定します。stateテーブルはこのボス上のこのパワーランタイムのフック間で保持されます。他のパワーは別のステートテーブルを持ちます。 - 戦闘ガード --
on_enter_combatはループ開始前にcontext.state.startedをチェックし、複数の重複ループを防ぎます。 - スケジューラパターン --
context.scheduler:run_every(100, callback)は初回を遅延ゼロでスケジュールし、その後100ティック(5秒)ごとに繰り返します。最初に5秒待つわけではありません。初回も待たせる場合はrun_repeating(100, 100, callback)を使います。コールバックは元のイベントやプレイヤーを含まない新しいコンテキストを受け取るため、loop_context.bossも新しいスナップショットです。返された数値のタスクIDをステートに保存します。この例はアニメーションを選ぶだけで、それ自体はダメージを与えません。 - 終了時のクリーンアップ --
on_exit_combatは保存したタスクIDで繰り返しタスクをキャンセルし、ステートをリセットします。これをしなければ戦闘終了後もスケジューラは動作を続けます。
戦闘中だけ必要な繰り返しタスクはon_exit_combatでキャンセルしてください。処理を省くと、戦闘終了後もキャンセルまたはパワーランタイムの終了までスケジュールされたままです。一時停止中のランタイムはタスクを保持したままコールバックをスキップします。未処理のコールバックエラーはランタイムを終了し、所有するタスクをキャンセルします。
例:ヒット時の火炎効果
この例で学べること: シンプルな「ヒット時にエフェクトを適用」パワー -- 戦闘パワーの最も一般的なパターン。
完全なパワーファイル(クリックで展開)
return {
api_version = 1,
on_player_damaged_by_boss = function(context)
-- Guard: the player may be nil in edge cases
if context.player == nil then
return
end
if not context.cooldowns:global_ready() then
return
end
-- Check and set a 60-tick (3 second) local cooldown in one call
if not context.cooldowns:check_local("fire_touch", 60) then
return
end
-- Coordinate with other powers that check the shared cooldown
context.cooldowns:set_global(40)
-- Set the player on fire for 60 ticks (3 seconds)
context.player:set_fire_ticks(60)
-- Visual feedback: spawn flame particles at the player's location
context.world:spawn_particle_at_location(
context.player.current_location,
{ particle = "FLAME", amount = 20, speed = 0.1 }
)
-- Tell the player what happened
context.player:send_message("&cThe boss's touch burns!")
end
}
解説
- Nilガード --
context.playerは現在のイベントのプレイヤーまたは直接ターゲットから解決されます。特に別のフックへ応用する場合は、使用前に確認してください。スケジューラのコールバックにはこのプレイヤーは引き継がれません。 - ローカルクールダウン --
global_ready()の確認後、context.cooldowns:check_local("fire_touch", 60)はキーを確認し、使用可能なら直ちに60ティックのクールダウンを設定します。使用できなければfalseを返してフックを終了します。同じボスのパワーはこのキーを共有しますが、別のボスとは独立しています。例を組み合わせる場合はキーにパワー名の接頭辞を付けてください。 - 燃焼時間 --
context.player:set_fire_ticks(60)はプレイヤーが有効、生存中、オンライン、発生元と同じワールド内であり、ダンジョンインスタンス内では現在の参加者でもあることを確認してから、Bukkitの燃焼時間を60ティック(3秒)に設定します。60ティックを加算せず、既存の時間を置き換えます。他のゲーム処理で消火される場合もあり、メッセージやパーティクルだけでは効果が適用された証拠になりません。 - パーティクル --
context.world:spawn_particle_at_location(location, spec)は指定位置にパーティクルを生成します。設定テーブルはparticle(Bukkitパーティクルenum名)、amount、speedを受け取ります。 - メッセージ --
context.player:send_message(text)は色付きチャットメッセージを送信します。&c(赤)などの標準Minecraftカラーコードは自動で処理されます。 - グローバルクールダウン --
context.cooldowns:set_global(40)は効果適用前にボスの共有クールダウンを40ティック(2秒)に設定します。この例はローカルクールダウンを消費する前に確認します。他のLuaフックやコールバックも待機させたい場合はglobal_ready()を呼ぶ必要があります。設定するだけですべてのLuaコールバックが停止するわけではありません。
例:ネイティブLuaゾーンを使用したゾーンベースAoEパワー
この例で学べること: ネイティブLuaゾーンを作成・クエリして、エリア内のプレイヤーにダメージを与える方法。
完全なパワーファイル(クリックで展開)
return {
api_version = 1,
on_spawn = function(context)
context.state.aoe_task_id = nil
end,
on_enter_combat = function(context)
-- Prevent duplicate loops
if context.state.aoe_task_id ~= nil then
return
end
context.state.aoe_task_id = context.scheduler:run_every(60, function(tick_context)
-- Make sure the boss is still alive
if not tick_context.boss.exists then
return
end
if not tick_context.cooldowns:global_ready() then
return
end
-- Check and set this boss's named cooldown before applying effects
if not tick_context.cooldowns:check_local("pulse_aoe", 60) then
return
end
tick_context.cooldowns:set_global(60)
-- Build a sphere zone centered on the boss's current position
local zone_def = {
kind = "sphere",
radius = 8,
origin = tick_context.boss:get_location()
}
-- Find all players inside the sphere
local victims = tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" })
-- Damage and show particles on each player found
for i = 1, #victims do
local victim = victims[i]
victim:deal_custom_damage(4.0)
tick_context.world:spawn_particle_at_location(
victim.current_location,
{ particle = "DUST", amount = 15, speed = 0, red = 128, green = 0, blue = 255 }
)
end
-- Spawn a particle burst at the boss; this does not draw the sphere
tick_context.world:spawn_particle_at_location(
tick_context.boss:get_location(),
{ particle = "WITCH", amount = 40, speed = 0.1 }
)
end)
end,
on_exit_combat = function(context)
if context.state.aoe_task_id ~= nil then
context.scheduler:cancel_task(context.state.aoe_task_id)
context.state.aoe_task_id = nil
end
end
}
解説
- ステート設定 --
on_spawnはaoe_task_idをnilに初期化します。ここに繰り返しタスクのIDを保持します。 - 繰り返し攻撃 --
on_enter_combatは初回遅延ゼロ、間隔60ティック(3秒)のタスクを開始します。ガードで重複ループを防ぎます。各コールバックはグローバルクールダウンを確認し、pulse_aoeを確認・設定してから、効果の前に60ティックのグローバルクールダウンを設定します。連携するのはこれらのキーを確認するパワーだけであり、すべてのスクリプトを自動停止するわけではありません。 - ゾーン定義 --
zone_defはネイティブLuaゾーン構文を使います。kindが形状("sphere")、radiusが大きさを指定し、originにはコールバック実行時のボスの現在位置を設定します。そのため移動中のボスにも追従します。 - エンティティクエリ --
tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" })は形状とエンティティの交差判定に基づく、添字1から始まるLua配列を返します。足元位置の距離だけを厳密に判定するものではありません。ボスの現在のワールドを検索し、プレイヤーのゲームモードや戦闘への参加状況では絞り込みません。フィルターは"players"、"elites"、"mobs"、"living"(既定)で、ボス自身は除外します。 - ダメージ付与 --
victim:deal_custom_damage(4.0)はBossCustomAttackDamage経由で、ボスに帰属する基本ダメージ4を送信します。無敵・死亡した対象と、サバイバルまたはアドベンチャー以外のプレイヤーは除外します。エリートからプレイヤーへの経路では通常のダメージ計算式と防御による軽減を迂回し、バニラ防具による軽減も解除します。ただし、ダメージリスナー、キャンセル、衝撃吸収で実際の体力減少は変わり得ます。ヘルパーは送信後に対象のネイティブダメージクールダウンをリセットします。context.script:damageとは別の経路です。 - パーティクル表示 -- ダメージが拒否された場合も、取得した各プレイヤーの保存済み位置に紫色のダストを表示します。ボス位置の
WITCHパーティクルは一度に生成されるだけで、リングや球体境界を描きません。この簡略パーティクルテーブルの位置の拡散は既定でゼロ、ダストサイズは1固定です。 - クリーンアップ --
on_exit_combatは繰り返しタスクをキャンセルし、ステートをクリアします。
この例はkind、radius、originなどの単純なテーブルを受け取るネイティブLuaゾーン(context.zones:get_entities_in_zone())を使用します。スクリプトユーティリティ(context.script:zone(...))はEliteScriptフィールド名(shape、Target、Target2)を使用します。単純な形状にはネイティブゾーンを、高度なターゲット解決が必要な場合はcontext.scriptを使ってください。
例:マルチフェーズボスメカニクス
この例で学べること: ステートを使用してボスのフェーズを追跡し、体力の閾値で行動を切り替える方法。
これはLuaステート上のフェーズであり、ボス定義を置き換えるEliteMobs YAMLのphases:ではありません。ボスの置換や回復は行いません。後から回復してもフェーズ1には戻らず、戦闘終了後もパワーランタイムが再作成されるまでフェーズ2を保持します。
完全なパワーファイル(クリックで展開)
local function phase_one_attack(context)
-- One damage pulse per callback; the animation does not delay it
context.boss:play_model_animation("slam")
local zone_def = {
kind = "sphere",
radius = 5,
origin = context.boss:get_location()
}
local targets = context.zones:get_entities_in_zone(zone_def, { filter = "players" })
for i = 1, #targets do
targets[i]:deal_custom_damage(3.0)
end
context.world:spawn_particle_at_location(
context.boss:get_location(),
{ particle = "EXPLOSION", amount = 3, speed = 0 }
)
end
local function phase_two_attack(context)
-- One pulse per callback, repeated more frequently in phase 2
context.boss:play_model_animation("frenzy")
local zone_def = {
kind = "sphere",
radius = 8,
origin = context.boss:get_location()
}
local targets = context.zones:get_entities_in_zone(zone_def, { filter = "players" })
for i = 1, #targets do
targets[i]:deal_custom_damage(2.0)
targets[i]:apply_potion_effect("SLOWNESS", 40, 1)
end
context.world:spawn_particle_at_location(
context.boss:get_location(),
{ particle = "DUST", amount = 30, speed = 0.2, red = 255, green = 0, blue = 0 }
)
context.world:play_sound_at_location(
context.boss:get_location(),
"entity.wither.ambient",
1.0, 1.5
)
end
local function start_attack_loop(context)
if context.state.attack_task_id ~= nil then return end
local phase = context.state.phase or 1
local interval = phase == 2 and 40 or 100
local attack = phase == 2 and phase_two_attack or phase_one_attack
context.state.attack_task_id = context.scheduler:run_every(interval, function(tick_context)
if not tick_context.boss.exists or not tick_context.boss.is_in_combat then return end
if tick_context.cooldowns:check_local("phase_attack", interval) then
attack(tick_context)
end
end)
end
return {
api_version = 1,
on_spawn = function(context)
context.state.phase = 1
context.state.attack_task_id = nil
end,
on_enter_combat = function(context)
start_attack_loop(context)
end,
on_game_tick = function(context)
-- Only check phase transition every 20 ticks (1 second) to stay lightweight
if not context.boss.is_in_combat then return end
if not context.cooldowns:check_local("phase_check", 20) then
return
end
-- Skip if already in phase 2
if context.state.phase ~= 1 then
return
end
-- Check health ratio
local health_ratio = context.boss.health / context.boss.maximum_health
if health_ratio <= 0.5 then
-- Transition to phase 2
context.state.phase = 2
context.log:info("Boss entering phase 2 at " .. tostring(math.floor(health_ratio * 100)) .. "% health")
-- Cancel the old attack loop
if context.state.attack_task_id ~= nil then
context.scheduler:cancel_task(context.state.attack_task_id)
context.state.attack_task_id = nil
end
-- Play transition effects
context.boss:play_model_animation("transform")
-- Announce the phase change to nearby players
local nearby = context.players.nearby_players(40)
for i = 1, #nearby do
nearby[i]:send_message("&4&lThe boss enters a frenzy!")
nearby[i]:show_title("&4Phase 2", "&cThe boss is enraged!", 10, 40, 10)
end
start_attack_loop(context)
end
end,
on_exit_combat = function(context)
if context.state.attack_task_id ~= nil then
context.scheduler:cancel_task(context.state.attack_task_id)
context.state.attack_task_id = nil
end
end
}
解説
- ステート初期化 --
on_spawnでphaseを1、attack_task_idをnilに設定します。このパワーランタイム専用の状態であり、他のパワーと共有したり再読み込み後に保存したりはしません。 - 攻撃ループ --
on_enter_combatはstart_attack_loopで現在のフェーズを再開します。フェーズ1の間隔は100ティック、フェーズ2は40です。どちらも初回遅延ゼロでスケジュールします。各攻撃にはphase_attackクールダウンの完了が必要で、フェーズ移行でも古いクールダウンは消えないため、攻撃をスキップするコールバックもあります。この例はグローバルクールダウンを確認しません。フェーズ1は半径5ブロックの球内のプレイヤーにカスタムダメージ3を送信し、アニメーション時間を待たずにアニメーションとパーティクルを再生します。 - on_game_tick でのフェーズ確認 --
on_game_tickは戦闘中でなければ即座に終了し、その後20ティックのクールダウン("phase_check")を確認します。すでにフェーズ2の場合も早期終了します。 - 体力閾値 --
context.boss.health / context.boss.maximum_healthで現在の体力を割合で取得します。50%以下になるとトランジションが始まります。 - フェーズトランジション -- 古い攻撃ループをキャンセルし、変身アニメーションを再生し、近くのプレイヤーにメッセージとタイトルを送信し、より速い新しい攻撃ループを開始します(100ティックではなく40ティックごと)。
- フェーズ2攻撃 --
phase_two_attackは半径8ブロックの球を使い、成功したコールバックごとに取得した各プレイヤーへダメージ2の攻撃を1回送信します。上述の参加条件を確認したうえで、移動速度低下IIを40ティック適用しようとします(amplifier = 1)。その後、赤いダストとウィザーの音を再生します。1回のコールバック内で複数の攻撃をスケジュールするものではありません。 - ログ --
context.log:info(...)はサーバーコンソールに書き込みます。 - クリーンアップ --
on_exit_combatはボスがどのフェーズにいるかに関係なく、現在アクティブな攻撃ループをキャンセルします。
有効で一時停止していないランタイムは、毎サーバーティック(20 TPSなら毎秒20回)on_game_tickを受け取ります。check_local("phase_check", 20)のようなクールダウンで重い処理を制限してください。体力は定期的な確認なので、致命傷によって移行確認前にボスが削除される場合があります。実行予算を超えるとそのパワーランタイムが終了し、所有するタスクもキャンセルされます。
AI生成のヒント
AIにLuaパワーを確実に生成させたい場合、プロンプトに以下を含めてください:
- 正確なフック名 -- 例:
on_player_damaged_by_boss、「ボスがプレイヤーを攻撃したとき」ではない。 - ネイティブLuaゾーンかスクリプトユーティリティか -- どちらを使うか指定します。ネイティブゾーンは
kind、radius、originを使うcontext.zones:get_entities_in_zone(zone_def, opts)です。スクリプトユーティリティはshape、Target、Target2などのEliteScriptフィールドを使うcontext.script:zone(...)です。 - ローカルおよびグローバルクールダウン -- パワー名を付けたキーとティック単位の期間を指定します。
context.cooldowns:check_local(key, ticks)はボス単位の名前付きクールダウンを確認・設定します。グローバルクールダウンと連携するにはローカルキーを消費する前にglobal_ready()を明示的に確認し、効果より先にグローバル期間を設定します。 - カスタムモデルアニメーション名 -- 例は
context.boss:play_model_animation("slam")です。AIはモデルのアニメーション名を推測できないので指定してください。 - ターゲット選択 --
context.player(イベントのプレイヤー)、context.players.nearby_players(range)、またはゾーンクエリのいずれかを指定します。 - エフェクトタイプ -- ダメージ(
deal_custom_damage)、ポーション(apply_potion_effect)、燃焼(set_fire_ticks)、速度(set_velocity_vector、apply_push_vector)などを指定します。 - 正しいラッパーのメソッド名 -- 対象となるボスAPIまたは共有APIのリファレンスを渡し、別名を創作しないよう指定します。
- スクリプトユーティリティのフィールド名 -- Lua風の名前ではなく、EliteScriptの
targetType、shape、Target、Target2、range、offset、coverageを使います。 - スケジューラの開始時刻とコンテキスト -- 初回を即時にするか遅延させるかを指定します。
run_everyは初回遅延ゼロで、run_repeatingは別途遅延を受け取ります。コールバック自身の新しいコンテキストを使います。元のプレイヤーやイベントは含まれないため、後で必要なプレイヤーUUIDや位置は明示的に保持します。
良いプロンプト例
on_enter_combatで初回遅延80ティック、間隔80ティックの繰り返しタスクを開始するLuaパワーを書いてください。各コールバックでボスを中心とする半径6のネイティブLua球体を作り、プレイヤーを取得してそれぞれにカスタムダメージ2.0を送信します。グローバルクールダウンを確認してからローカルキー"my_power:pulse"を80ティックで確認・設定し、効果の前にグローバルクールダウンも80に設定します。on_exit_combatでタスクをキャンセルします。取得した各プレイヤーにDUSTパーティクル(red=0, green=255, blue=100)を生成します。
追加で含めるべき制約
- 「
api_version = 1を持つ単一のテーブルを返すこと。」 - 「すべてのステートフィールドを
on_spawnで初期化すること。」 - 「スケジューラのコールバックは
if not tick_context.boss.exists then return endでガードすること。」 - 「繰り返しタスクはすべて
on_exit_combatでキャンセルすること。」 - 「メソッド名を創作しないこと -- APIリファレンスのページにあるメソッドのみを使うこと。」
- 「チェックとセットをまとめて行うには
context.cooldowns:check_local(key, ticks)を使うこと。」
QCチェックリスト
デプロイ前に次の項目でLuaパワーを確認してください。
- ファイルは
api_version = 1を含む1つのテーブルのみを返す。 - すべてのフック名はフックリストのエントリと正確に一致する(例:
on_player_damaged_by_bossであり、on_player_hitではない)。 context.playerはnilになりうるフックで使用前に== nilでガードされている。context.stateフィールドはon_spawnで初期化されている。run_everyとrun_repeatingのどちらで作成した場合も、戦闘中だけ必要な繰り返しタスクはon_exit_combatでキャンセルされ、初回遅延も意図どおりである。- スケジューラコールバックは自身のコンテキストを使い、元のプレイヤーやイベントがあると仮定していない。
- クールダウンキーにはパワー名の接頭辞があり、期間はティック単位。グローバル連携には明示的な準備完了チェックがある。
on_game_tickフックはクールダウンチェックの後ろに重い処理を隠している。- すべてのメソッド名はAPIリファレンスに存在し、創作した別名はない。
- スクリプトユーティリティテーブルはLua風の名前ではなく、EliteScriptフィールド名(
targetType、shape、Targetなど)を使用している。 - ネイティブゾーン定義は
kind、radius、origin、destinationなどを使用している。 - パワーはフックやコールバック内でブロッキングまたは長時間実行操作を呼び出していない。
- パーティクル仕様は有効なBukkitパーティクルenum名をUPPER_CASEで使用している(例:
"FLAME"、"DUST"、"EXPLOSION")。 - 変更したファイルを再読み込みし、新しいボスをスポーンさせ、ステージングサーバーで見た目の動作とコンソール出力を確認する。構文解析の成功だけでは攻撃の動作は証明できない。
ベストプラクティス
-
小さなフックから始めて検証する。 単一の
on_spawnでログメッセージを送信するものを書く。発火を確認してから構築を進める。 -
ヘルパー関数はローカルに保つ。
local function pick_action(context)のように返却テーブルの上で宣言し、依存関係を明確にする。各パワーインスタンスは既に独立したLuaランタイムを持つ。 -
ジオメトリはスクリプトユーティリティに入れる。 コーン、回転レイ、平行移動レイ、アニメーションゾーンが必要な場合は、EliteScriptフィールド名で
context.script:zone(...)を使用する。スクリプトユーティリティはEliteScriptのゾーンエンジンを再利用する。 -
ランタイムステートには
context.stateを使用する。 範囲は1体のボス上の1つのパワーランタイムであり、そのランタイムのフック間で保持される。他のパワーとは共有せず、再読み込み後にも保存されない。 -
名前付きローカルクールダウンキーを使用する。 単なる数値の代わりに
"fire_touch"や"aoe_pulse"のような説明的なキーを使う。明示したキーは同じボス上の全パワーで共有されるため、"my_power:fire_touch"のようにパワー名を付けて衝突を防ぐ。 -
on_game_tickを軽量に保つ。 クールダウンで重い処理を制限し、実行予算に十分な余裕を持たせてください。 -
繰り返しタスクが終わったらキャンセルする。 戦闘専用ループは
on_exit_combatでキャンセルする。所有するスケジューラタスクはランタイム終了時に自動キャンセルされるが、明示的にキャンセルしなければ戦闘終了後も続く。 -
スケジューラコールバックでは新しいコンテキストを使用する。
run_every、run_repeating、run_afterは、元のイベントやプレイヤーを含まない新しいコンテキストを渡す。その引数を使い、必要なUUIDや位置は明示的に保持する。外側のコンテキストには古いスナップショットが残ることがある。 -
context.log:info()でステートの遷移を記録する。 開発中はフェーズ切り替え、クールダウン開始、スケジューラの開始・停止を記録する。デプロイ前に頻繁なログを削除する。ボスのcontext.log:debug()も接頭辞が変わるだけでinfoレベルに出力される。 -
関連するEliteScriptリファレンスを再利用する。 ゾーン、ターゲット、相対ベクトルは共有パーサーを説明している。YAMLの条件、待機、アクション単位のキャッシュは独立したLuaヘルパーで自動評価されないため、確認処理をLuaで記述する。継承ターゲットや着地位置には、これらのハンドルが持たないYAMLアクションコンテキストが必要になる。
よくある初心者の間違い
-
スケジューラコールバック内で外側の
contextを使用する。 外側のコンテキストはフックが実行された時点のスナップショットをキャプチャする。run_everyやrun_afterコールバック内では、常にコールバック独自のパラメータ(例:tick_context)を使用する。これによりフレッシュなスナップショットが得られる。 -
繰り返しタスクのキャンセルを忘れる。
on_enter_combatでrun_everyを開始してもキャンセルしなければ、戦闘終了後もボスがサーバーから削除されるまでタスクが実行され続ける。 -
on_spawnでステートを初期化しない。on_game_tickでcontext.state.phaseを読み取るがon_spawnで設定しなかった場合、nilになり比較が予期しない動作をする。 -
nilガードなしで
context.playerを使用する。 どのフックがプレイヤーを提供するか確認する。スケジュールされたコールバックは既定ではプレイヤーを持たず、ガードがないとパワーランタイムが終了する場合がある。 -
スクリプトユーティリティでLua風のフィールド名を使用する。 文書化されたEliteScriptの
targetType、Target、Target2、shapeを使う。target_typeやzone_shapeなどのアンダースコア表記は未対応。ゾーンパーサーは小文字のtargetやtarget2を受け付けるが、任意のLua風の別名を使えるわけではない。 -
クールダウンなしで
on_game_tickに重い処理を置く。 多数のボスでワールド全体のエンティティ検索や高密度のパーティクル形状を処理すると負荷が高い。実行頻度を制限し、各コールバックを実行予算内に収める。 -
異なるラッパーAPIを混ぜる。 EliteMobsボス専用ラッパーは
entity:teleport_to_location(loc)とplayer:set_velocity_vector(vec)を使う。共有MagmaCoreラッパーには追加の別名もある。他のプラグインの例から借りる前に、そのコンテキストのリファレンスを確認する。 -
context.boss.healthで体力を設定する。context.boss.healthは読み取り専用のスナップショット。ボスを回復するにはcontext.boss:restore_health(amount)を使用する。 -
api_version = 1を忘れる。 返却テーブルにはこのフィールドが含まれている必要があり、そうでなければEliteMobsはパワーをロードしない。
次のステップ
- はじめに -- ファイル構造、フック、最初のパワーの解説
- NPCスクリプト -- NPCの接近・操作スクリプト
- フックとライフサイクル -- 完全なフックとコンテキストリファレンス
- ボスとエンティティ -- ボス、プレイヤー、エンティティメソッド
- ワールドと環境 -- パーティクル、サウンド、雷、ブロック
- ゾーンとターゲティング -- ネイティブゾーンとスクリプトユーティリティゾーン
- Enum -- Particle、Sound、Material、その他の文字列定数の有効な値
- トラブルシューティング -- よくある問題、デバッグのヒント、移行アドバイス
