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

Lua-скриптинг: Начало работы

webapp_banner.jpg

Что вы узнаете​

Эта страница научит вас писать первый Lua-пауэр для EliteMobs -- от пустого файла до работающей способности босса. К концу вы поймете хуки, контекст, кулдауны и общую структуру каждого файла Lua-пауэра.

Освоив основы, переходите к сопутствующим страницам:

Всё ещё развивается

Большинство встроенных способностей EliteMobs используют Lua, но имена вспомогательных методов и поведение могут меняться между версиями. Проверяйте свои способности на тестовом сервере перед использованием на рабочем и перечитывайте справочник после обновления.

Связь с EliteScript

Lua не заменяет существующую YAML-систему eliteScript:, и EliteScript не устарел.

  • Используйте EliteScript, когда вам нужен декларативный YAML-скриптинг с существующими страницами Действий, Целей, Зон, Условий, Кулдаунов и Относительных векторов.
  • Используйте файлы Lua-пауэров, когда нужны переменные, циклы, случайный выбор, многоразовые вспомогательные функции, персистентное состояние для каждого босса и более традиционный программный поток.

API Script Utilities (context.script) передаёт таблицы спецификаций классам целей, зон, относительных векторов и частиц EliteScript. Их правила разбора и ограничения сохраняются, но самостоятельные Lua-объекты не располагают всеми контекстами YAML-действий. Унаследованным целям и позициям приземления нужен отсутствующий у этих объектов контекст. Lua-методы также не применяют автоматически ожидания, условия и кэширование целей YAML-действий. Страница о зонах и нацеливании объясняет эти различия.


Что такое Lua-пауэры​

Lua-пауэры -- это автономные файлы .lua, расположенные в обычном дереве каталогов powers EliteMobs и ссылающиеся точно так же, как обычные файлы пауэров.

Пауэры боссов vs. NPC-скрипты

Эта страница обучает Lua-пауэрам боссов. NPC-скрипты на Lua -- это отдельная функция: они находятся в plugins/EliteMobs/npc_scripts/, подключаются через список scripts: в конфиге NPC и используют специфичные для NPC хуки, такие как on_npc_proximity_enter. См. NPC-скрипты.

В чем Lua-пауэры хороши​

Lua-пауэры отлично подходят, когда вам нужны:

  • Ротации атак и случайный выбор приемов
  • Персистентное состояние между хуками с context.state
  • Отложенные и повторяющиеся действия без построения всего из YAML-ожиданий
  • Пользовательские вспомогательные функции внутри одного файла
  • Сложное ветвление, которое было бы неудобно в чистом EliteScript
  • Логика босса, которая хочет переиспользовать определения целей и зон в стиле EliteScript

Если ваш пауэр в основном "сработало событие, выполнить пару скриптовых действий", существующие документы EliteScript по-прежнему самый быстрый и понятный способ. Если пауэру нужен настоящий программный поток, Lua -- инструмент для этого.


Для кого эта страница​

Эта страница написана для трех типов читателей:

  • Тех, кто уже знает EliteScript и хочет изучить Lua, не изучая "настоящее программирование" сразу целиком
  • Тех, кто новичок в скриптинге EliteMobs и нуждается в полном справочнике с точными именами
  • Тех, кто использует ИИ для создания пауэров и нуждается в достаточной детализации, чтобы понять, когда ИИ выдумал что-то ложное

Вам не нужно становиться полноценным Lua-разработчиком, чтобы писать полезные пауэры. Для большинства практических пауэров EliteMobs вам нужно только:

  • Как разместить допустимый хук в возвращаемой таблице
  • Как читать значения из context
  • Как досрочно выйти с помощью if ... then return end
  • Как правильно вызвать несколько вспомогательных методов
  • Как скопировать правильные спецификации целей и зон из существующих документов EliteScript

Ментальная модель: EliteScript vs. Lua​

Если вы знаете EliteScript, это сравнение -- самый быстрый путь к пониманию Lua-пауэров:

Если вы мыслите в терминах EliteScriptВ Lua это обычно означает
EventsИмена хуков, такие как on_spawn или on_boss_damaged_by_player
Cooldownscontext.cooldowns (см. context.cooldowns ниже)
ActionsПрямые вызовы методов, например context.world:spawn_particle_at_location(...) или context.script:damage(...)
Targetscontext.script:target({...}) -- использует имена полей EliteScript Target
Zonescontext.script:zone({...}) или нативные хелперы context.zones -- см. Зоны и нацеливание
Relative vectorscontext.script:relative_vector({...}) или context.vectors
Script flowВаши собственные if в Lua, вспомогательные функции, таймеры и состояние

Главное отличие:

  • EliteScript описывает в YAML, что должно произойти.
  • Lua позволяет вам решать с помощью кода, когда, почему и какая ветвь должна выполняться.

Если EliteScript ощущается как "конфигурация", то Lua ощущается как "конфигурация плюс принятие решений".


Краткий курс Lua​

Большинство способностей используют несколько понятий Lua: переменные (local x = 5), функции (function foo() end), проверки if (if x then ... end), таблицы ({key = value}) и nil (отсутствие значения). Точки с запятой необязательны. Таблицы используют фигурные скобки, а функции и блоки управления завершаются словом end.

Полный разбор с примерами см. в MagmaCore Lua Scripting Engine — Краткий курс Lua. Этот курс общий для всех плагинов Nightbreak, так что изучив его один раз, вы сможете применять его везде.


Ваш первый работающий пауэр, шаг за шагом​

Если вы совершенно новичок, это лучшая прогрессия для "первой победы".

Перед шагом 1: Сохраните и прикрепите к боссу​

Сохраните Lua-файл в обычную папку пауэров EliteMobs, например:

plugins/
EliteMobs/
powers/
first_test.lua

Затем добавьте имя файла в конфиг босса, используя обычный список powers::

powers:
- first_test.lua

Полный цикл для новичка:

  1. Сохранить файл в plugins/EliteMobs/powers/
  2. Добавить имя файла .lua в список powers: босса
  3. Выполнить /em reload, чтобы загрузить изменённые файлы, затем создать нового тестового босса. Перезагрузка затрагивает плагин и его активный контент, поэтому используйте тестовый сервер.
  4. Вызвать хук и проверить видимый результат и консоль. Сохранение файла или создание другого босса без перезагрузки не обновляет уже загруженное определение.

Если нужна дополнительная информация о файлах боссов, списках пауэров или структуре кастомных боссов, см. Создание кастомных боссов. Эта страница подробно разбирает настройку боссов, включая то, как работает список powers:.

Шаг 1: Заставить файл загрузиться​

return {
api_version = 1,

on_spawn = function(context)
end
}

Загрузчик проверяет синтаксис Lua, возвращаемую таблицу и имена хуков. У пустого хука нет видимого результата, поэтому отсутствие ошибок само по себе не доказывает, что файл найден или подключён. Добавьте звук из шага 2 и создайте нового босса, чтобы проверить весь путь.

Шаг 2: Заставить босса сделать что-то видимое​

return {
api_version = 1,

on_spawn = function(context)
context.boss:play_sound_at_self("entity.blaze.ambient", 1.0, 1.0)
end
}

Теперь у вас есть самое важное доказательство для новичка: ваш хук срабатывает.

Шаг 3: Отреагировать на удар игрока​

return {
api_version = 1,

on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end

context.player:send_message("&eYou hit the boss.")
end
}

Это учит трем ключевым идеям:

  • on_boss_damaged_by_player -- это имя хука
  • context.player -- это игрок, участвующий в хуке
  • return досрочно выходит, когда нужные данные отсутствуют

Шаг 4: Предотвратить спам с помощью кулдауна​

return {
api_version = 1,

on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end

if not context.cooldowns:local_ready("hello_message") then
return
end

context.player:send_message("&eYou woke up the boss.")
context.cooldowns:set_local(60, "hello_message")
end
}

Один тик локальной задержки равен 50 миллисекундам прошедшего времени: 60 — три секунды даже при снижении TPS сервера. Эта именованная задержка общая для способностей одного босса, но не для разных боссов. При сочетании собственных способностей добавляйте к ключам префикс с именем способности. Пример проверяет только свой локальный ключ, а не глобальную задержку босса.

Шаг 5: Добавить реальный эффект​

return {
api_version = 1,

on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end

if not context.cooldowns:local_ready("shock") then
return
end

context.cooldowns:set_local(100, "shock")
context.player:send_message("&cStatic jumps from the boss into your armor!")
context.world:strike_lightning_at_location(context.player.current_location)
end
}

Это создаёт настоящую молнию в мире, а не только визуальную вспышку. Задержка устанавливается до эффекта, чтобы вызванные им события уже видели её. Эта локальная задержка в 100 тиков равна пяти секундам прошедшего времени.


Первый реальный рабочий процесс​

При создании нового Lua-пауэра используйте такой порядок:

  1. Создать файл и заставить on_spawn работать.
  2. Переключиться на нужный хук.
  3. Убедиться, что хук содержит ожидаемые данные, такие как context.player или context.event.
  4. Сначала добавить сообщение или звук, до урона или частиц.
  5. Добавить кулдауны.
  6. Добавить один игровой эффект.
  7. Только после этого добавлять хелперы, состояние, логику планировщика или зоны.

Такой порядок значительно упрощает отладку, потому что за раз меняется только одна вещь.


Где хранятся Lua-файлы​

Размещайте файлы .lua в том же дереве папок, что и обычные файлы пауэров .yml:

plugins/
EliteMobs/
powers/
mycoolpower.lua
attack_push.yml
subfolder/
myotherpower.lua

Lua-пауэры автоматически обнаруживаются из каталогов пауэров, которые EliteMobs уже загружает.

Необязательные метаданные YAML​

Рядом с собственной mycoolpower.lua в том же каталоге может находиться загруженная mycoolpower.yml или mycoolpower.yaml. При обнаружении читаются её поля isEnabled, effect, powerType, powerCooldown и globalCooldown; isEnabled: false запрещает регистрацию этого собственного Lua-файла. Без соответствующих метаданных эффект отсутствует, а тип способности — MISCELLANEOUS. Lua-хуки всё равно сами управляют моментом атаки и должны самостоятельно проверять задержки.

Загруженный YAML-файл с тем же базовым именем рядом со встроенной Lua-способностью применяет к ней те же поля. Без такого файла встроенная способность сохраняет поставляемые метаданные. О полях см. Создание способностей.

Как боссы ссылаются на Lua-пауэры​

Файлы боссов по-прежнему используют обычный список powers::

powers:
- attack_push.yml
- mycoolpower.lua

Специальных полей не требуется. Lua-пауэры не загружаются через eliteScript:.

Правила именования файлов​

  • Боссы ссылаются на Lua-пауэры по имени файла, а не по пути к папке.
  • EliteMobs в настоящее время регистрирует обнаруженные Lua-пауэры только по базовому имени.
  • Избегайте дублирования имен, например powers/fire.lua и powers/bosses/fire.lua. EliteMobs загружает только тот файл, чей полный путь идёт первым по алфавиту, и выводит предупреждение о пропущенной копии, если только оба файла не идентичны.

Готовые Lua-пауэры​

Большинство встроенных способностей написаны на Lua. Текущий исходный код определяет 68 конкретных готовых Lua-способностей, которые устанавливаются в plugins/EliteMobs/powers/, если файлы отсутствуют. Среди них — attack_fire.lua, frost_cone.lua, meteor_shower.lua и shield_wall.lua. Изучайте их как примеры, учитывая, что изменённая локальная копия может отличаться от версии в составе плагина.

Готовые файлы записываются только при отсутствии, поэтому правки сохраняются после перезапуска. Когда обновление меняет встроенную способность, неизменённая копия прежней поставляемой версии при запуске перемещается в plugins/MagmaCore/outdated files/EliteMobs/, а на её место записывается текущая версия; изменённые копии остаются. После удаления встроенного файла следующая инициализация плагина восстановит поставляемую версию; перед заменой сохраните нужные собственные изменения.

Общие фрагменты исходного кода​

Lua-способности боссов могут включать вспомогательный файл директивой-комментарием на отдельной строке:

-- @include helpers/common.inc

Загрузчик вставляет текст указанного файла до проверки способности. Пути отсчитываются от файла с директивой и должны оставаться внутри каталога основного файла способности, в том числе после разрешения символических ссылок. Циклические включения, вложенность глубже 16 уровней, файлы больше 4 МиБ и развёрнутый код длиннее 4 194 304 символов отклоняются.

Для фрагментов используйте расширение, отличное от .lua, например .inc, чтобы механизм обнаружения не пытался загрузить их ещё и как самостоятельные способности. Объявляйте вспомогательные функции перед последней таблицей return. Эта директива относится только к загрузке способностей боссов: она не включает Lua require или доступ к файловой системе, а NPC-скрипты её не обрабатывают.

Устаревшие имена .yml по-прежнему разрешаются​

Конфиги боссов со ссылкой на attack_fire.yml продолжают работать. Для ссылок .yml и .yaml EliteMobs сначала ищет загруженную Lua-способность с тем же базовым именем, затем использует буквальное имя. Файл, который отключён или не прошёл проверку, не является загруженной заменой. Соблюдайте написание и регистр базового имени. Чтобы изменить встроенную Lua-способность, отредактируйте её существующий файл и перезагрузите плагин; одинаковые базовые имена в других каталогах не обеспечивают надёжное переопределение.


Минимальный контракт файла​

Каждый Lua-пауэр должен возвращать таблицу с помощью return. Эта таблица намеренно строгая.

Обязательные и необязательные поля верхнего уровня​

ПолеОбязательноТипПримечания
api_versionДаNumberИспользуйте целое число 1
priorityНетNumberИспользуйте целое число. Меньшие значения выполняются первыми; по умолчанию 0. Приоритет YAML-способностей — 0
Поддерживаемые ключи хуковНетFunctionДолжен использовать одно из точных имен хуков, перечисленных в Справочнике хуков

Правила валидации​

  • Файл должен возвращать таблицу.
  • api_version обязательно и в настоящее время должно быть 1.
  • priority должен быть числовым, если присутствует.
  • Каждый дополнительный ключ верхнего уровня должен быть поддерживаемым именем хука.
  • Каждый ключ хука должен указывать на функцию.
  • Неизвестные ключи верхнего уровня отклоняются.

Это означает, что вспомогательные функции и локальные константы должны находиться выше финального return, а не внутри возвращаемой таблицы, если они не являются реальными хуками.

Тело файла выполняется при проверке, а затем повторно в отдельной Lua-среде каждого экземпляра способности на боссе. context существует только как аргумент хука или колбэка, а не как глобальная переменная верхнего уровня. Локальные переменные могут сохраняться в этом экземпляре, но значения этапа проверки в него не переносятся. Среда убирает доступ к файлам, ОС и Java, а также require; для фрагментов кода используйте директиву включения. Необработанная ошибка хука или колбэка закрывает этот экземпляр и отменяет его задачи; следующий удар не запускает повторную попытку. Исправьте файл и перезагрузите перед новой проверкой.


Шаблоны для копирования​

Минимальный валидный Lua-пауэр​

return {
api_version = 1,

on_spawn = function(context)
end
}
local ATTACK_COOLDOWN = "my_attack"

local function can_run_attack(context)
return context.cooldowns:local_ready(ATTACK_COOLDOWN)
and context.cooldowns:global_ready()
end

local function run_attack(context)
context.boss:play_sound_at_self("entity.blaze.shoot", 1.0, 1.0)
context.cooldowns:set_local(100, ATTACK_COOLDOWN)
context.cooldowns:set_global(20)
end

return {
api_version = 1,
priority = 0,

on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end

if not can_run_attack(context) then
return
end

run_attack(context)
end
}

Структура для больших файлов​

local CONSTANT_NAME = "value"

local function helper_function(context)
end

local function another_helper(context, value)
end

return {
api_version = 1,
priority = 0,

on_spawn = function(context)
end,

on_enter_combat = function(context)
end,

on_boss_damaged_by_player = function(context)
end,

on_exit_combat = function(context)
end
}

Хуки​

Хуки -- это специально именованные функции в таблице, которую вы возвращаете. EliteMobs вызывает их, когда что-то происходит -- босс спавнится, получает урон, вступает в бой и т.д. Вы уже видели on_spawn и on_boss_damaged_by_player в уроке выше.

Самые распространенные хуки для начала -- on_spawn, on_boss_damaged_by_player, on_enter_combat и on_exit_combat. Полный список всех хуков, доступные ключи контекста в каждом и порядок выполнения см. в Хуки и жизненный цикл.


Что такое context?​

Каждая функция хука получает один аргумент -- context. Представьте его как набор инструментов, который EliteMobs передает вам каждый раз, когда что-то происходит -- он содержит все необходимое для взаимодействия с боссом, игроком, миром, кулдаунами и многим другим.

on_boss_damaged_by_player = function(context)
-- context.boss = the boss that was hit
-- context.player = the player who hit it
-- context.world = tools for spawning particles, sounds, etc.
-- context.cooldowns = cooldown management
-- context.state = your own persistent storage
end

Вы не создаете context сами -- EliteMobs создает его и передает в ваш хук. Вы просто читаете из него и вызываете методы.

к сведению

Каждый хук получает новый context. Таблица context.state принадлежит экземпляру этой способности на этом боссе и хранит данные для последующих хуков и колбэков. У других способностей своё состояние; закрытие или перезагрузка экземпляра удаляет его состояние.


Ключевые API context​

Вот самые важные API, которые вы будете использовать из context:

  • context.state -- Обычная Lua-таблица, сохраняемая для экземпляра этой способности на этом боссе. Храните в ней номера фаз, ID задач и флаги между хуками. После удаления или перезагрузки экземпляра данные не сохраняются.

  • context.log -- Вывод в консоль через log:info(msg), log:warn(msg) и log:debug(msg). Включайте имя способности в сообщение, когда нужно различать вывод нескольких способностей.

  • context.cooldowns -- Именованные задержки и глобальная задержка, общие для способностей босса. Явные ключи используют единое пространство имён босса: добавляйте к ним префикс с именем способности. Ключ nil выбирает задержку по умолчанию для этого имени Lua-файла. cooldowns:check_local(key, ticks) проверяет и устанавливает локальную задержку одним вызовом; глобальную он не проверяет и не устанавливает. Полный API см. в Хуки и жизненный цикл.

  • context.scheduler -- scheduler:run_after(ticks, callback) откладывает один колбэк. scheduler:run_every(ticks, callback) повторяет вызовы без ожидания первого интервала; для явной начальной задержки используйте scheduler:run_repeating(delay, interval, callback). Колбэки получают новый контекст без исходного события и игрока. Используйте этот аргумент, а нужный UUID игрока или позицию сохраняйте явно; не рассчитывайте на наличие ctx.player после задержки. Повторы только для боя отменяйте в on_exit_combat; иначе задачи продолжаются до отмены или закрытия экземпляра. См. Хуки и жизненный цикл.

  • context.boss / context.player -- Босс и игрок, участвующие в текущем событии. См. Босс и сущности для всех полей и методов.

  • context.world -- Спавн частиц, сущностей, звуков, молний, блоков. См. Мир и окружение.

  • context.zones / context.script -- Геометрия зон, нацеливание, урон, частицы. См. Зоны и нацеливание.


Синтаксис методов: : vs. .​

Разница между : и . описана в MagmaCore Lua Scripting Engine. Методы сущностей и общие сервисы принимают обе формы. Для context.event.set_damage_amount(value) и context.event.multiply_damage_amount(multiplier) нужна точка: двоеточие передаст таблицу события первым аргументом. context.event:transfer_damage(target, amount) принимает двоеточие. Глобальные конструкторы, например em.create_location(...), используют точку.


Следующие шаги​

Теперь, когда вы знаете основы, изучите остальную документацию по Lua-скриптингу:

Для YAML-скриптинга страницы EliteScript остаются каноническим справочником: