Lua-скриптинг: Хуки и жизненный цикл
Эта страница описывает каждый хук, который может определить Lua-пауэр, порядок выполнения хуков, как каждый босс получает собственную изолированную среду выполнения, и какие функции стандартной библиотеки доступны внутри песочницы.
Если вы ещё не писали Lua-пауэр, начните с Начало работы.
Эта страница документирует хуки для Lua-пауэров боссов в plugins/EliteMobs/powers/. Lua-скрипты NPC используют собственную папку plugins/EliteMobs/npc_scripts/ и специфичные для NPC хуки, такие как on_npc_interact и on_npc_proximity_enter; см. Скрипты NPC.
Справочник хуков
Каждый файл Lua-пауэра возвращает таблицу. Каждый ключ в этой таблице (кроме api_version и priority) должен быть одним из хуков, перечисленных ниже. Среда выполнения вызывает соответствующую функцию всякий раз, когда срабатывает соответствующее игровое событие.
| Hook | Fires when | context.player available? |
|---|---|---|
on_spawn | Спавнится elite-моб | Нет |
on_game_tick | Один раз за серверный тик (50 мс), пока активны часы среды выполнения | Нет |
on_boss_damaged | Босс получает урон из любого источника | Нет |
on_boss_damaged_by_player | Босс получает урон от игрока | Да |
on_boss_damaged_by_elite | Босс получает урон от другого elite-моба | Нет |
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. Созданные в Lua наблюдатели из context.zones:watch_zone(...) и context.script:zone(...):watch(...) вызывают свои коллбэки on_enter / on_leave напрямую вместо вызова этих хуков верхнего уровня.
Типичный пауэр с несколькими хуками
Один Lua-пауэр может определять столько хуков, сколько ему нужно. Ниже приведён скелет, использующий три хука вместе:
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.
Модель среды выполнения
Одна среда выполнения на босса
Каждая сущность босса получает собственный независимый экземпляр среды выполнения Lua. Когда босс спавнится, EliteMobs загружает исходный код Lua, выполняет его в свежей изолированной среде песочницы и сохраняет возвращённую таблицу. Когда босс деспавнится или удаляется, среда выполнения завершается.
Это означает:
- Глобальные переменные Lua, установленные во время выполнения файла (например, вспомогательные функции с
local function), являются приватными для этого босса. - Функции хуков возвращаемой таблицы никогда не разделяются между боссами.
Изоляция состояния
У каждой среды выполнения есть собственная таблица context.state. Состояние одного босса полностью невидимо для любого другого босса, даже если они используют один и тот же файл 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 (или Java-исключение всплывает из вызова API), пауэр немедленно отключается для этого экземпляра босса. Среда выполнения завершается, и все принадлежащие задачи отменяются.
Ошибка записывается в консоль сервера вместе с именем файла пауэра, номером строки и хуком, который выполнялся:
[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.
Бюджет выполнения
Каждый вызов хука и каждый вызов коллбэка засекается по времени. Если один вызов занимает больше 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]) | Создать таблицу локации с опциональным именем мира, рысканием (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) | Создать определение прямоугольной зоны (cuboid) |
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) | Создать определение зоны перемещающегося луча |
Конструкторы зон возвращают цепочечные (chainable) таблицы с :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 для всех строковых констант
