Lua-скриптинг: Скрипты NPC
Lua-скрипты NPC в EliteMobs -- это автономные файлы .lua, которые прикрепляются к конфигам NPC. Они отделены от Lua-пауэров боссов: пауэры боссов лежат в plugins/EliteMobs/powers/, а скрипты NPC -- в plugins/EliteMobs/npc_scripts/.
Теперь скрипты NPC выполняются в той же единой среде выполнения скриптов MagmaCore, что и пауэры боссов, пропы FreeMinecraftModels и предметы FMM. Это значит, что скрипт NPC получает всю общую скриптовую поверхность -- context.world (включая strike_lightning), context.zones, context.scheduler, context.cooldowns, context.log, context.event и context.player -- плюс специфичную для NPC таблицу context.npc. Всё, что MagmaCore предоставляет скриптам, доступно и здесь.
Lua-скрипты NPC всё ещё экспериментальны. Специфичные для NPC хуки и помощники context.npc могут измениться. Общие таблицы (context.world, context.zones, context.scheduler, context.cooldowns, context.log, context.event, context.player) -- это те же самые, что задокументированы в разделах Движок скриптинга и Справочник Lua API.
Расположение файлов
Создавайте файлы скриптов NPC в:
plugins/
EliteMobs/
npc_scripts/
wave.lua
Подпапки сканируются рекурсивно. Однако скрипты регистрируются только по имени файла, поэтому npc_scripts/wave.lua и npc_scripts/town/wave.lua конфликтуют -- держите базовые имена уникальными по всему дереву.
Расширение .lua в конфиге NPC необязательно: и - wave, и - wave.lua разрешаются в wave.lua. Если конфиг NPC ссылается на несуществующий скрипт, EliteMobs выводит предупреждение, а NPC всё равно появляется.
Прикрепление скриптов к NPC
Добавьте список scripts: в конфиг NPC:
scripts:
- wave.lua
К одному NPC можно прикрепить несколько скриптов:
scripts:
- wave.lua
- greeting_particles.lua
Скрипты выполняются в порядке приоритета. Меньшие значения priority выполняются первыми. Если приоритет не указан, по умолчанию используется 0.
Структура скрипта
Каждый скрипт NPC должен возвращать одну таблицу:
return {
api_version = 1,
priority = 0,
on_spawn = function(context)
context.state.spawned = true
context.npc:play_model_animation("idle")
end
}
Принимаются только эти поля верхнего уровня:
| Поле | Тип | Примечания |
|---|---|---|
api_version | number | Обязательное. Должно быть 1. |
priority | number | Необязательное. Меньшие значения выполняются первыми. |
on_spawn | function | Выполняется после появления NPC. |
on_remove | function | Выполняется при удалении NPC. |
on_game_tick | function | Выполняется каждый тик сервера, пока NPC валиден. Держите его очень лёгким. |
on_npc_interact | function | Выполняется, когда игрок взаимодействует с NPC. |
on_npc_proximity_enter | function | Выполняется один раз, когда игрок входит в радиус активации этого NPC. |
on_npc_proximity_leave | function | Выполняется один раз, когда игрок покидает радиус активации этого NPC. |
on_zone_enter | function | Выполняется, когда игрок входит в зону, за которой следит этот скрипт (см. context.zones). |
on_zone_leave | function | Выполняется, когда игрок покидает отслеживаемую зону. |
Отслеживание зон учитывает только игроков -- мобы и другие сущности никогда не вызывают on_zone_enter / on_zone_leave.
Неизвестные ключи верхнего уровня отклоняются при загрузке скрипта. Вспомогательные функции следует объявлять как local-функции выше возвращаемой таблицы.
Хуки близости
Хуки близости NPC используют значение конфига activationRadius этого NPC.
| Хук | Когда срабатывает |
|---|---|
on_npc_proximity_enter | Игрок перемещается из-за пределов радиуса активации NPC внутрь него. |
on_npc_proximity_leave | Игрок перемещается изнутри радиуса активации NPC за его пределы. |
Эти хуки отслеживаются отдельно для каждого NPC и каждого игрока серверным сканером близости. Нахождение рядом с одним NPC не мешает другому NPC вызвать своё собственное событие входа, а пребывание внутри радиуса не приводит к спаму повторяющихся событий входа.
Обычное поведение приветствий, диалогов и индикаторов заданий по-прежнему работает. Lua-хук добавляет поведение поверх него.
Общая скриптовая поверхность
Поскольку скрипты NPC работают в единой среде выполнения, каждый хук NPC также получает общие таблицы контекста MagmaCore, используемые скриптами FreeMinecraftModels. Пауэры боссов EliteMobs работают в той же среде выполнения, но используют специфичные для боссов варианты нескольких таблиц. Полные списки методов находятся в Справочнике Lua API и Движке скриптинга:
| Таблица | Что она делает |
|---|---|
context.world | Эффекты и запросы мира: strike_lightning, spawn_particle, play_sound, set_block_at, place_temporary_block, spawn_entity, spawn_firework, get_nearby_entities, get_nearby_players, raycast и другие. Принимаются обе формы -- координатная (strike_lightning(x, y, z)) и с таблицей локации (strike_lightning_at_location(loc)). |
context.zones | Создание пространственных зон (create_sphere(x, y, z, radius), create_cylinder(x, y, z, radius, height), create_cuboid(x, y, z, xSize, ySize, zSize)) -- каждая возвращает числовой дескриптор. watch(handle, on_enter, on_leave) запускает отслеживание (коллбэки вызывают ваши хуки on_zone_enter / on_zone_leave, а не переданные вами функции); unwatch(handle) останавливает его. |
context.scheduler | run_later(ticks, fn), run_repeating(delay, interval, fn), cancel(task_id). |
context.cooldowns | Общие кулдауны MagmaCore: local_ready, local_remaining, check_local, set_local, global_ready, set_global. |
context.log | info(msg), warn(msg), error(msg) — пишет в консоль сервера. |
context.event | Текущее событие Bukkit, если оно есть. См. ниже. |
context.player | Взаимодействующий/вызвавший срабатывание игрок, если он есть. См. ниже. |
context.state | Обычная Lua-таблица, которая сохраняется для этого экземпляра скрипта NPC до удаления NPC. |
Пример: удар молнией при взаимодействии
return {
api_version = 1,
on_npc_interact = function(context)
-- NPC scripts can now reach the full world API.
context.world:strike_lightning_at_location(context.npc:get_location())
end
}
context.npc
context.npc доступен в каждом хуке NPC.
Поля
| Поле | Тип | Примечания |
|---|---|---|
name | string | Отображаемое имя NPC из конфига. |
filename | string | Имя файла конфига NPC. |
uuid | string | UUID NPC во время выполнения. |
activation_radius | number | Настроенный радиус активации. |
current_location | таблица локации | Снимок локации, когда стоящая за NPC сущность существует. |
entity_type | string | Тип сущности Bukkit, когда стоящая за NPC сущность существует. |
Методы
| Метод | Аргументы | Возвращает | Примечания |
|---|---|---|---|
is_valid() | - | boolean | Есть ли у NPC всё ещё валидная стоящая за ним сущность. |
get_location() | - | таблица локации | Текущая локация NPC или локация спавна, если сущность недоступна. |
get_eye_location() | - | таблица локации | Текущая локация глаз или запасной вариант -- локация спавна. |
get_activation_radius() | - | number | Текущий настроенный радиус активации. |
get_nearby_players(radius) | number | table | Обёртки игроков в пределах радиуса от NPC. |
face_direction_or_location(target) | вектор или локация | nil | Поворачивает по вектору направления или в сторону локации/локации игрока. |
say_greeting(player?) | player, UUID, имя или nil | nil | Отправляет настроенное приветствие. По умолчанию -- вызвавшему срабатывание игроку, если он доступен. |
say_dialog(player?) | player, UUID, имя или nil | nil | Отправляет настроенный диалог. По умолчанию -- вызвавшему срабатывание игроку, если он доступен. |
say_farewell(player?) | player, UUID, имя или nil | nil | Отправляет настроенный текст прощания. По умолчанию -- вызвавшему срабатывание игроку, если он доступен. |
play_model_animation(name) | string | nil | Воспроизводит анимацию кастомной модели, если она существует. Иначе безопасно ничего не делает. |
patrol_pause() | - | boolean | Приостанавливает настроенный патруль. |
patrol_resume() | - | boolean | Завершает ожидание или временное движение и возобновляет патруль. |
walk_to(x, y, z) | три числа | boolean | Идёт к смещению от исходной точки и возобновляет патруль. Длинные маршруты рассчитываются автоматически. |
hold(x, y, z) | три числа | boolean | Идёт к смещению и остаётся там. |
teleport(x, y, z) | три числа | boolean | Телепортируется, если в точке назначения обрабатываются сущности. |
Методы движения возвращают false, если у NPC нет настроенного патруля или запрос нельзя принять. См. Патрули NPC и боссов.
context.player
context.player доступен в on_npc_interact, on_npc_proximity_enter и on_npc_proximity_leave. Он равен nil в хуках жизненного цикла, не связанных с игроком.
Это общая обёртка игрока MagmaCore — та же полная таблица живой сущности/игрока, которую используют пауэры боссов и скрипты FMM, поэтому она предоставляет гораздо больше базового набора (здоровье, эффекты зелий, send_message, show_title, show_action_bar, get_held_item, рейкастинг и другое). Полный список см. в Справочнике Lua API. Чаще всего здесь используются:
| Поле / Метод | Примечания |
|---|---|
name | Имя игрока. |
uuid | UUID игрока. |
current_location | Таблица текущей позиции игрока; это поле, а не метод. |
get_eye_location() | Текущая локация глаз игрока. |
send_message(text) | Отправляет сообщение в чат. Поддерживает цветовые коды. |
Всегда проверяйте context.player на nil перед использованием в общих вспомогательных функциях.
entity_type в нижнем регистреВ общих таблицах сущностей MagmaCore entity_type — это имя Bukkit в нижнем регистре ("player", "zombie"). Только context.npc.entity_type и таблицы сущностей в пауэрах боссов EliteMobs используют форму в верхнем регистре. Если скрипту нужно работать с обоими вариантами, сравнивайте без учёта регистра.
Поля EliteMobs, добавляемые в каждую общую таблицу сущности
Пока EliteMobs запущен, он добавляет дополнительные поля в каждую таблицу сущности MagmaCore — в context.player, в обёртки, возвращаемые context.npc:get_nearby_players(...), и в те, что видят скрипты пропов и предметов FreeMinecraftModels:
| Поле | Тип | Примечания |
|---|---|---|
is_elite | boolean | true, если EliteMobs отслеживает сущность как элиту |
is_custom_boss | boolean | true, если это Кастомный босс (всегда false, когда is_elite равно false) |
is_significant_boss | boolean | true для Кастомного босса, у которого множитель здоровья больше 1 — практическая проверка «это настоящий босс, а не подкрепление» |
elite | table или nil | Присутствует только у элит. См. ниже |
Подтаблица elite:
| Поле / Метод | Тип | Примечания |
|---|---|---|
elite.level | number | Уровень элиты |
elite.name | string или nil | Отображаемое имя элиты |
elite.health | number | Текущее здоровье элиты (читается в реальном времени) |
elite.max_health | number | Максимальное здоровье элиты (читается в реальном времени) |
elite.is_custom_boss | boolean | То же значение, что и у поля верхнего уровня |
elite.health_multiplier | number | Настроенный множитель здоровья |
elite.damage_multiplier | number | Настроенный множитель урона |
elite:remove() | — | Убирает элиту из мира |
-- Warn the approaching player if a real boss is loose near this NPC
on_npc_proximity_enter = function(context)
if context.player == nil then return end
local here = context.npc:get_location()
local nearby = context.world:get_nearby_entities(here.x, here.y, here.z, 40)
for i = 1, #nearby do
if nearby[i].is_significant_boss then
context.player:send_message("&cA boss is nearby: " .. tostring(nearby[i].elite.name))
return
end
end
end
Эти поля не появляются в обёртках сущностей пауэров боссов EliteMobs, которые строятся отдельным построителем таблиц на стороне босса — этот набор см. в Боссы и сущности.
context.event
context.event равен nil, когда у хука нет события Bukkit. Когда оно есть, это общая таблица событий MagmaCore:
| Поле / Метод | Примечания |
|---|---|
is_cancelled | Отменено ли базовое событие (имеет смысл только для отменяемых событий). |
cancel() | Отменяет событие, если оно отменяемое. |
uncancel() | Снимает отмену события, если оно отменяемое. |
player | Действующее лицо события (например, взаимодействующий игрок) в виде обёртки игрока, если оно есть. |
Для взаимодействующего игрока или игрока, вошедшего в радиус, предпочтительнее context.player (он установлен для этих хуков).
Состояние, планировщик и кулдауны
context.state -- это обычная Lua-таблица, которая сохраняется для этого экземпляра скрипта NPC до удаления NPC.
context.scheduler -- это общий планировщик MagmaCore. Работают как имена MagmaCore, так и имена EliteMobs run_after / run_every -- это псевдонимы для одного и того же поведения:
| Метод | Аргументы | Примечания |
|---|---|---|
run_later(ticks, callback) / run_after(ticks, callback) | number, function | Выполняется один раз после задержки. Возвращает ID задачи. |
run_repeating(delay, interval, callback) | number, number, function | Выполняется повторно после начальной задержки. Возвращает ID задачи. |
run_every(interval, callback) | number, function | Выполняется каждые interval тиков (начальная задержка 0). Возвращает ID задачи. |
cancel(task_id) / cancel_task(task_id) | number | Отменяет принадлежащую скрипту задачу. |
Коллбэки планировщика получают свежий контекст. Они не получают исходные context.player или context.event. Все принадлежащие скрипту задачи автоматически отменяются при удалении NPC.
context.cooldowns -- это общая таблица кулдаунов MagmaCore:
| Метод | Аргументы | Возвращает | Примечания |
|---|---|---|---|
local_ready(key?) | string | boolean | True, когда локальный кулдаун истёк. |
local_remaining(key?) | string | number | Оставшиеся тики или 0, когда готово. |
check_local(key?, duration) | string, number | boolean | Если готово, запускает кулдаун и возвращает true. |
set_local(duration, key?) | number, string | nil | Устанавливает или сбрасывает кулдаун. |
global_ready() | - | boolean | True, когда общий глобальный кулдаун готов. |
set_global(duration) | number | nil | Запускает глобальный кулдаун. |
Скрипты NPC теперь используют общий порядок аргументов кулдаунов MagmaCore (check_local(key?, duration)), такой же, как у пауэров боссов и скриптов FreeMinecraftModels. Более ранние экспериментальные сборки NPC использовали check_local(duration, key?) — обновите старые скрипты под общий порядок.
Пример: приветственный жест при приближении
Этот скрипт заставляет NPC повернуться к вошедшему игроку и воспроизвести анимацию кастомной модели wave. Событие входа в радиус уже срабатывает один раз на пару NPC/игрок, пока игрок остаётся внутри радиуса; кулдаун не даёт быстрым циклам выхода/повторного входа проигрывать анимацию слишком часто.
return {
api_version = 1,
priority = 0,
on_npc_proximity_enter = function(context)
if context.player == nil then return end
if context.cooldowns:check_local("wave:" .. context.player.uuid, 60) then
context.npc:face_direction_or_location(context.player.current_location)
context.npc:play_model_animation("wave")
end
end
}
play_model_animation(name) безопасно ничего не делает, когда у NPC нет кастомной модели или в модели нет такой анимации.
Рекомендации по производительности
- Держите хуки
on_game_tickнебольшими. Они выполняются 20 раз в секунду для каждого экземпляра скрипта NPC, который их определяет. (Скрипты, не объявляющиеon_game_tick, не тикаются вообще.) - Для поведения, зависящего от близости, предпочитайте
on_npc_proximity_enterиon_npc_proximity_leaveвместо опроса ближайших игроков каждый тик. - Используйте
context.cooldowns:check_local(...), чтобы ограничивать анимации, звуки и всплески частиц. - Используйте
context.scheduler:run_repeating(...)с разумным интервалом, когда поведению не нужно выполняться каждый тик. - Избегайте больших поисков в Lua.
context.npc:get_nearby_players(radius)подходит для небольших локальных проверок, но широкое сканирование должно оставаться в среде выполнения плагина.
Связанные страницы
- Создание NPC -- поля конфига NPC, включая
activationRadius - Начало работы с Lua -- Lua-пауэры боссов
- Движок скриптинга -- общие концепции Lua и единая среда выполнения
- Справочник Lua API -- полный список методов для
context.world,context.player,context.zonesи другого
