Luaスクリプティング:フックとライフサイクル
このページでは、Luaパワーが定義できるすべてのフック、フックの実行順序、各ボスが独自の分離されたランタイムを取得する方法、およびサンドボックス内で利用可能な標準ライブラリ関数について説明します。
まだLuaパワーを書いたことがない場合は、まずはじめにから始めてください。
フックリファレンス
Every Lua power file returns a table. Each key in that table (besides api_version and priority) must be one of the hooks listed below. The runtime calls the matching function whenever the corresponding game event fires.
| Hook | Fires when | context.player available? |
|---|---|---|
on_spawn | The elite mob spawns | No |
on_game_tick | Once every server tick (50 ms) while the runtime clock is active | No |
on_boss_damaged | The boss takes damage from any source | No |
on_boss_damaged_by_player | The boss takes damage from a player | Yes |
on_boss_damaged_by_elite | The boss takes damage from another elite mob | No |
on_player_damaged_by_boss | A player takes damage from this boss | Yes |
on_enter_combat | The boss enters combat | Yes |
on_exit_combat | The boss leaves combat | No |
on_heal | The boss heals | No |
on_boss_target_changed | The boss switches its target | Yes |
on_death | The boss dies | No |
on_phase_switch | A phase boss switches to a new phase | No |
When context.player is listed as "No", accessing it returns nil. Always nil-check before using it.
典型的なマルチフックパワー
A single Lua power can define as many hooks as it needs. Below is a skeleton that uses three hooks together:
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)
-- すべての飛び道具ダメージを半減
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 | ゾーンに入るまたは出るエンティティ |
キャンセル可能なイベント(一般)
基盤となるゲームイベントがキャンセル可能なフックはすべて event.cancel_event() を公開します。特定のフック(例:on_game_tick、on_heal)で context.event が nil の場合、操作対象となる基盤イベントは存在しません。
エンティティテーブルの全フィールドについてはボスとエンティティを参照してください。ダメージ原因とスポーン理由の値については列挙型と値を参照してください。
フック実行順序
When a boss has multiple Lua powers attached, each power's hook is called for the same event. The order is determined by the priority field:
- Lower values run first (default is
0). - Powers with the same priority run in load order (effectively unspecified).
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
}
Priority only affects ordering among Lua powers on the same boss. It does not interact with EliteScript execution order.
ランタイムモデル
ボスごとに1つのランタイム
Every boss entity gets its own independent Lua runtime instance. When the boss spawns, EliteMobs loads the Lua source, evaluates it in a fresh sandboxed environment, and stores the returned table. When the boss despawns or is removed, the runtime is shut down.
This means:
- Global Lua variables set during file evaluation (like helper functions with
local function) are private to that boss. - The returned table's hook functions are never shared between bosses.
状態の分離
Each runtime has its own context.state table. One boss's state is completely invisible to every other boss, even if they share the same Lua power file. Use context.state to store counters, flags, timers, or any per-boss data you need across hooks.
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
}
スケジュールされたタスクの所有権
All tasks created through context.scheduler are owned by the runtime that created them. When a boss despawns:
- The runtime calls
shutdown(). - Every owned task -- both one-shot (
run_after) and repeating (run_every) -- is automatically cancelled. - All zone watches are cleared.
You never need to manually clean up scheduled tasks on boss removal. However, you should still cancel repeating tasks when they are no longer needed during normal gameplay to avoid unnecessary work:
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
}
ティックごとのクロック動作
The internal tick clock for a Lua power instance only runs when there is a reason to tick. Specifically, the clock is registered when either:
- The power defines an
on_game_tickhook, or - The power has created at least one zone watch via
context.zones:watch_zone(...).
If neither condition is true, no per-tick overhead is incurred. The clock is unregistered automatically when the boss despawns or the runtime shuts down.
エラーとパフォーマンスの動作
EliteMobs enforces strict error and performance limits on Lua powers:
例外
If a hook function or a scheduled callback throws a Lua error (or a Java exception surfaces from an API call), the power is immediately disabled for that boss instance. The runtime is shut down and all owned tasks are cancelled.
The error is logged to the server console along with the filename of the power:
[EliteMobs] Lua power frost_cone.lua failed while handling on_boss_damaged_by_player.
実行バジェット
Every hook invocation and every callback invocation is timed. If a single call takes longer than 50 milliseconds, the power is disabled with a console warning:
[EliteMobs] Lua power my_power.lua exceeded the 50ms execution budget on on_game_tick and was disabled.
This prevents runaway scripts from freezing the server. To stay within budget:
- Avoid unbounded loops inside hooks. Use
context.scheduler:run_every(...)to spread work across ticks. - Keep
on_game_tickhandlers lightweight -- they run every single tick. - Move heavy initialization into
on_spawnoron_enter_combatrather than repeating it every tick.
Luaサンドボックス
Lua powers run inside a sandboxed LuaJ environment. Several globals that could access the filesystem or the Java runtime are removed.
削除されたグローバル
The following standard Lua globals are set to nil and cannot be used:
| Removed | Why |
|---|---|
debug | Exposes internal VM state |
dofile | Filesystem access |
io | Filesystem access |
load | Arbitrary code loading |
loadfile | Filesystem access |
luajava | Direct Java class access |
module | Module system (not needed) |
os | Operating system access |
package | Module system (not needed) |
require | Module system / filesystem access |
利用可能な標準ライブラリ
Everything else from the Lua standard library works normally:
| Category | Functions |
|---|---|
| Math | math.abs, math.ceil, math.floor, math.max, math.min, math.random, math.sin, math.cos, math.sqrt, math.pi, and all other math.* functions |
| String | string.byte, string.char, string.find, string.format, string.gsub, string.len, string.lower, string.match, string.rep, string.sub, string.upper, and all other string.* functions |
| Table | table.insert, table.remove, table.sort, table.concat, and all other table.* functions |
| 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 writes to the server console, but prefer context.log:info(msg) or context.log:warn(msg) for output. These are prefixed with the power name, making it easier to trace which power produced the message.
em ヘルパー名前空間
The em table is available at file load time (before any hook runs). It provides helper constructors for building location tables, vector tables, and zone definitions used throughout the API.
| Function | Purpose |
|---|---|
em.create_location(x, y, z [, world, yaw, pitch]) | Create a location table with optional world name, yaw, and pitch |
em.create_vector(x, y, z) | Create a vector table |
em.zone.create_sphere_zone(radius) | Create a sphere zone definition |
em.zone.create_dome_zone(radius) | Create a dome zone definition |
em.zone.create_cylinder_zone(radius, height) | Create a cylinder zone definition |
em.zone.create_cuboid_zone(x, y, z) | Create a cuboid zone definition |
em.zone.create_cone_zone(length, radius) | Create a cone zone definition |
em.zone.create_static_ray_zone(length, thickness) | Create a static ray zone definition |
em.zone.create_rotating_ray_zone(length, point_radius, animation_duration) | Create a rotating ray zone definition |
em.zone.create_translating_ray_zone(length, point_radius, animation_duration) | Create a translating ray zone definition |
Zone builders return chainable tables with :set_center(loc) (or :set_origin(loc) / :set_destination(loc) depending on zone type). These are designed to be used at the top of a file or inside hooks:
-- 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
}
For a full breakdown of zone shapes, filters, watchers, and targeting patterns, see Zones & Targeting.
次のステップ
- はじめに | ボスとエンティティ | ワールドと環境 | ゾーンとターゲティング | サンプル | Enum | トラブルシューティング
- APIリファレンス | スクリプティングエンジン
