Перейти к основному содержимому

Lua-скриптинг: Скрипты NPC

webapp_banner.jpg

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_versionnumberОбязательное. Должно быть 1.
prioritynumberНеобязательное. Меньшие значения выполняются первыми.
on_spawnfunctionВыполняется после появления NPC.
on_removefunctionВыполняется при удалении NPC.
on_game_tickfunctionВыполняется каждый тик сервера, пока NPC валиден. Держите его очень лёгким.
on_npc_interactfunctionВыполняется, когда игрок взаимодействует с NPC.
on_npc_proximity_enterfunctionВыполняется один раз, когда игрок входит в радиус активации этого NPC.
on_npc_proximity_leavefunctionВыполняется один раз, когда игрок покидает радиус активации этого NPC.
on_zone_enterfunctionВыполняется, когда игрок входит в зону, за которой следит этот скрипт (см. context.zones).
on_zone_leavefunctionВыполняется, когда игрок покидает отслеживаемую зону.

Отслеживание зон учитывает только игроков -- мобы и другие сущности никогда не вызывают 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.schedulerrun_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.loginfo(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.

Поля

ПолеТипПримечания
namestringОтображаемое имя NPC из конфига.
filenamestringИмя файла конфига NPC.
uuidstringUUID NPC во время выполнения.
activation_radiusnumberНастроенный радиус активации.
current_locationтаблица локацииСнимок локации, когда стоящая за NPC сущность существует.
entity_typestringТип сущности Bukkit, когда стоящая за NPC сущность существует.

Методы

МетодАргументыВозвращаетПримечания
is_valid()-booleanЕсть ли у NPC всё ещё валидная стоящая за ним сущность.
get_location()-таблица локацииТекущая локация NPC или локация спавна, если сущность недоступна.
get_eye_location()-таблица локацииТекущая локация глаз или запасной вариант -- локация спавна.
get_activation_radius()-numberТекущий настроенный радиус активации.
get_nearby_players(radius)numbertableОбёртки игроков в пределах радиуса от NPC.
face_direction_or_location(target)вектор или локацияnilПоворачивает по вектору направления или в сторону локации/локации игрока.
say_greeting(player?)player, UUID, имя или nilnilОтправляет настроенное приветствие. По умолчанию -- вызвавшему срабатывание игроку, если он доступен.
say_dialog(player?)player, UUID, имя или nilnilОтправляет настроенный диалог. По умолчанию -- вызвавшему срабатывание игроку, если он доступен.
say_farewell(player?)player, UUID, имя или nilnilОтправляет настроенный текст прощания. По умолчанию -- вызвавшему срабатывание игроку, если он доступен.
play_model_animation(name)stringnilВоспроизводит анимацию кастомной модели, если она существует. Иначе безопасно ничего не делает.
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Имя игрока.
uuidUUID игрока.
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_elitebooleantrue, если EliteMobs отслеживает сущность как элиту
is_custom_bossbooleantrue, если это Кастомный босс (всегда false, когда is_elite равно false)
is_significant_bossbooleantrue для Кастомного босса, у которого множитель здоровья больше 1 — практическая проверка «это настоящий босс, а не подкрепление»
elitetable или nilПрисутствует только у элит. См. ниже

Подтаблица elite:

Поле / МетодТипПримечания
elite.levelnumberУровень элиты
elite.namestring или nilОтображаемое имя элиты
elite.healthnumberТекущее здоровье элиты (читается в реальном времени)
elite.max_healthnumberМаксимальное здоровье элиты (читается в реальном времени)
elite.is_custom_bossbooleanТо же значение, что и у поля верхнего уровня
elite.health_multipliernumberНастроенный множитель здоровья
elite.damage_multipliernumberНастроенный множитель урона
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?)stringbooleanTrue, когда локальный кулдаун истёк.
local_remaining(key?)stringnumberОставшиеся тики или 0, когда готово.
check_local(key?, duration)string, numberbooleanЕсли готово, запускает кулдаун и возвращает true.
set_local(duration, key?)number, stringnilУстанавливает или сбрасывает кулдаун.
global_ready()-booleanTrue, когда общий глобальный кулдаун готов.
set_global(duration)numbernilЗапускает глобальный кулдаун.
Unified cooldown API

Скрипты 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) подходит для небольших локальных проверок, но широкое сканирование должно оставаться в среде выполнения плагина.

Связанные страницы