Lua-скриптинг: Скрипты NPC
Lua-скрипты NPC в EliteMobs -- это автономные файлы .lua, которые прикрепляются к конфигам NPC. Они отделены от Lua-пауэров боссов: пауэры боссов лежат в plugins/EliteMobs/powers/, а скрипты NPC -- в plugins/EliteMobs/npc_scripts/.
Скрипты NPC используют общую среду выполнения MagmaCore вместе с пауэрами боссов и пропами FreeMinecraftModels. Они получают общие сервисы context.world, context.zones, context.scheduler, context.cooldowns и context.log, а также таблицу context.npc для NPC. Данные игрока и события зависят от хука. У пауэров боссов есть собственные варианты нескольких таблиц, поэтому в скриптах NPC используйте сигнатуры общего API.
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. При равном приоритете сохраняется порядок списка scripts:. Это порядок обработки событий NPC, а не независимых задач планировщика.
Структура скрипта
Каждый скрипт 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 | Выполняется каждый тик сервера после инициализации среды скрипта и запуска её таймера. Обработчик должен быть лёгким. |
on_npc_interact | function | Выполняется, когда обычный обработчик взаимодействия доходит до шага скриптов; ограничения описаны ниже. |
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 выполняет его снова в собственной песочнице. Не используйте контекст хука при инициализации на уровне файла. Глобальные переменные и context.state отдельны у каждого скрипта, в том числе у скриптов одного NPC.
Среда NPC инициализируется лениво. Сейчас хост NPC создаёт экземпляры и отправляет on_spawn, но не вызывает метод немедленной инициализации start(). Поэтому файл, содержащий только on_game_tick, сам не начинает тикать. Добавьте on_spawn = function(context) end, чтобы инициализировать его при появлении, либо дождитесь другого объявленного хука события.
Ошибка Lua закрывает затронутый экземпляр скрипта и отменяет принадлежащие ему задачи. Последующие события NPC отбрасывают закрытый экземпляр, а не создают его заново. Перезагрузите исправленное определение и создайте новый экземпляр скрипта NPC. Состояние не сохраняется после удаления или перезапуска; on_remove — хук удаления NPC, а не универсальный обработчик ошибок или завершения.
Хуки близости
Хуки близости NPC используют значение конфига activationRadius этого NPC.
| Хук | Когда срабатывает |
|---|---|
on_npc_proximity_enter | Игрок перемещается из-за пределов радиуса активации NPC внутрь него. |
on_npc_proximity_leave | Игрок перемещается изнутри радиуса активации NPC за его пределы. |
Серверный сканер проверяет каждую пару NPC/игрок каждые 100 тиков, обычно раз в пять секунд при 20 TPS, по сферическому расстоянию от текущей сущности NPC. Если игрок уже внутри при первой проверке, это считается входом. Короткое посещение между проверками может остаться незамеченным. Близость к одному NPC не мешает событию входа другого, а непрерывное обнаружение внутри не вызывает повторных входов.
NPC, которые сейчас движутся по патрулю или редактируются, пропускаются. Поэтому ранее обнаруженная пара может дать событие выхода при начале движения и входа при возобновлении обнаружения. Удаление NPC и выход игрока с сервера не гарантируют хук выхода: для отправки нужны и отслеживаемый NPC, и игрок онлайн. Пустой набор NPC или игроков онлайн очищает отслеживание без событий выхода. activationRadius не выше нуля отключает активацию по близости; отдельный радиус маркера квестодателя в 30 блоков не расширяет активацию Lua.
Обычное поведение приветствий, диалогов и индикаторов заданий по-прежнему работает. 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). Возвращает числовой дескриптор либо nil, если у владельца нет мира. watch(handle, on_enter, on_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 | Данные события или действующего лица, если хук их предоставляет. См. ниже. |
context.player | Взаимодействующий/вызвавший срабатывание игрок, если он есть. См. ниже. |
context.state | Обычная Lua-таблица, которая сохраняется для этого экземпляра скрипта NPC до удаления NPC. |
Общие зоны используют абсолютные координаты в мире NPC на момент создания и не следуют за его перемещением. Создание сразу регистрирует ежетиковую проверку игроков в текущем мире владельца, даже до установки обработки входа/выхода через watch. Передайте функцию для каждого нужного направления и реализуйте соответствующий on_zone_enter или on_zone_leave в возвращаемой таблице скрипта. Эти хуки получают context.player и таблицу события с действующим лицом, но без идентификатора зоны. unwatch полностью удаляет дескриптор; для повторного отслеживания создайте другую зону. Игроки внутри при первой проверке считаются вошедшими, а игрок офлайн не может получить коллбэк выхода.
Пример: удар молнией при взаимодействии
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 | Поворачивает в направлении или к позиции в том же мире. Таблица с world или current_location считается позицией, а одни координаты — направлением. Ничего не делает во время активного движения патруля. |
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 | Телепортируется к смещению от заданной исходной точки, если там обрабатываются тики сущностей. |
remove() | - | nil | Удаляет текущую сущность NPC обычным путём с причиной OTHER, не удаляя заданное размещение. Не вызывайте из on_remove: это повторно запустит удаление. |
Методы движения возвращают 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_player | table или nil | В общих обёртках игрока, созданных EliteMobs, предоставляет tier, если элитный инвентарь игрока загружен. Каждое обращение создаёт новую таблицу; сохранённая таблица сохраняет прежний tier. |
can_receive_hostile_effect(actor_uuid) | boolean | Проверяет игрока-инициатора по общим правилам враждебных целей и разрешениям магического оружия EliteMobs. Передайте строку с корректным UUID. |
Подтаблица 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 создаются другим построителем и имеют иной набор полей. Некоторые имена совпадают, например is_elite, но общая вложенная таблица elite не взаимозаменяема с методами пауэров боссов. Их набор см. в Боссы и сущности. FMM предоставляет собственный адаптер EliteMobs; не считайте, что каждое дополнительное поле общей обёртки, созданной EliteMobs, существует и в FMM.
context.event
context.event равен nil, только если у хука нет ни события, ни действующего лица. Это общая таблица событий MagmaCore:
| Поле / Метод | Примечания |
|---|---|
is_cancelled | Снимок состояния отмены при создании таблицы; false для неотменяемых событий. Не обновляется после cancel() или uncancel(). |
cancel() | Есть только для отменяемого базового события. Отменяет его. |
uncancel() | Есть только для отменяемого базового события. Снимает отмену. |
player | Действующее лицо события (например, взаимодействующий игрок) в виде обёртки игрока, если оно есть. |
Для взаимодействующего игрока или игрока, вошедшего в радиус, предпочтительнее context.player (он установлен для этих хуков).
Сейчас on_npc_interact передаёт игрока без события Bukkit. Поэтому таблица события содержит player и is_cancelled: false, но не методы cancel или uncancel. Скрипт взаимодействия с NPC не может отменить обычное меню NPC через эту таблицу.
Обработка взаимодействий игнорирует уже отменённые события Bukkit, блокирует использование бирок и взаимодействие с редактируемыми NPC, а также подавляет повторные взаимодействия одного игрока в течение тика. Если цель задания с диалогом обрабатывает взаимодействие, выполнение возвращается до Lua-хука. Иначе хук запускается до обычного меню/действия NPC. События близости также неотменяемые; хуки зон передают действующее лицо без события Bukkit. Эти пути тоже не предоставляют cancel() или uncancel().
Состояние, планировщик и кулдауны
context.state принадлежит отдельному экземпляру скрипта 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 | Запускает глобальный кулдаун. |
У check_local длительность всегда второй аргумент: для ключа по умолчанию используйте check_local(nil, 60). У set_local, наоборот, длительность идёт первой. Ключ по умолчанию включает имя файла скрипта. Явно заданные локальные ключи и хранилище глобального кулдауна общие для скриптов одного адаптера NPC; используйте разные ключи, если не нужна совместная работа. Длительность измеряется монотонным прошедшим временем из расчёта 50 мс на номинальный тик, а не числом серверных тиков. Неположительная длительность снимает кулдаун. Установка глобального кулдауна не блокирует автоматически хуки или check_local; проверяйте global_ready() самостоятельно.
Скрипты NPC теперь используют общий порядок аргументов кулдаунов MagmaCore (check_local(key?, duration)), такой же, как у пауэров боссов и скриптов FreeMinecraftModels. Более ранние экспериментальные сборки NPC использовали check_local(duration, key?) — обновите старые скрипты под общий порядок.
Пример: приветственный жест при приближении
Этот скрипт поворачивает NPC к игроку, чей вход обнаружен, и воспроизводит анимацию кастомной модели wave. Вход определяется проверками раз в пять секунд, а не каждым пакетом движения. Кулдаун с ключом ограничивает повторы анимации при повторных уведомлениях о входе, но не повышает частоту сканирования.
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 раз в секунду. Скрипт без этого хука всё равно может выполнять ежетиковые проверки зон или явно запланированные задачи. - Для поведения, зависящего от близости, предпочитайте
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и другого
