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

Lua-скриптинг: Устранение неполадок

webapp_banner.jpg

Эта страница охватывает распространённые проблемы, с которыми вы можете столкнуться при написании или отладке Lua-способностей, а также советы по миграции для авторов, переходящих с EliteScript. Если вы отлаживаете NPC-скрипты, см. NPC-скрипты. Для рабочих примеров см. Примеры и паттерны. Если вы только начинаете, см. Начало работы.

Общий движок Lua

EliteMobs использует движок Lua-скриптинга MagmaCore, общий для плагинов Nightbreak. Документацию по общим концепциям, таким как песочница, планировщик, зоны, API мира, таблицы сущностей и методы UI игрока, см. на странице Движок Lua-скриптинга MagmaCore.


Распространённые проблемы​

1. Способность вообще не загружается​

Проверьте серверную консоль на наличие ошибок при запуске сервера. Самая частая причина — синтаксическая ошибка Lua (отсутствие end, несовпадающие скобки и т.д.). Также убедитесь, что файл заканчивается на .lua и находится в правильном каталоге powers.

Для собственных имён файлов проверьте, не задано ли isEnabled: false в файле метаданных .yml или .yaml с тем же базовым именем в том же каталоге: это исключает файл из обнаружения. Файл метаданных с тем же базовым именем рядом со встроенной способностью тоже применяет к ней свои поля isEnabled, effect, powerType и поля перезарядки. Если два файла способностей с одинаковым именем лежат в разных папках, загружается только один; консоль указывает пропущенную копию.

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

2. Хук никогда не срабатывает​

Убедитесь, что имя хука написано точно так, как указано в списке хуков. Частые ошибки: on_boss_hit (неправильно) vs. on_boss_damaged_by_player (правильно), или on_tick (неправильно) vs. on_game_tick (правильно).

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

3. context.player равен nil​

context.player заполняется только у тех хуков, чьё базовое событие несёт игрока. Он всегда равен nil в on_spawn, on_game_tick, on_boss_damaged, on_boss_damaged_by_elite, on_exit_combat, on_heal, on_death, on_phase_switch и on_mind_action.

Он заполняется в on_boss_damaged_by_player, on_reinforcement_damaged_by_player, on_player_damaged_by_boss, on_enter_combat и on_boss_target_changed, а также в on_zone_enter / on_zone_leave, когда участвующая сущность является игроком. Всегда проверяйте nil перед использованием. Полную таблицу см. в Хуках и жизненном цикле.

4. Таймаут / превышен бюджет выполнения​

Каждый хук, запланированный коллбэк и вычисление файла делят с вложенными вызовами бюджет в 250 000 инструкций Lua и 50 мс процессорного времени потока. Если JVM не может измерить это время, используется резервный предел в 250 мс прошедшего времени. Ошибка указывает превышенный предел:

Lua instruction budget exceeded (250000 instruction limit)
Lua CPU-time budget exceeded (50ms current-thread CPU limit)
Lua elapsed-time fallback budget exceeded (250ms fallback; current-thread CPU time unavailable)

VM прерывает неуправляемые циклы Lua во время выполнения, но не может прервать выполняющийся вызов Java API. Отдельного предела в 50 мс реального времени после возврата вызова нет. Частые причины: перебор слишком большого числа сущностей, частицы в большой зоне без coverage и тяжёлая работа каждый тик. Используйте перезарядки, меньше точек выборки или context.scheduler:run_every(...), чтобы распределить работу между тиками.

5. Колбэк планировщика использует устаревшие данные​

Вы, вероятно, используете внешний context вместо параметра колбэка. Измените function() ... context.boss ... end на function(tick_context) ... tick_context.boss ... end.

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

6. Запрос зоны не возвращает сущности​

Дважды проверьте определение зоны. Для нативных зон убедитесь, что kind указан в нижнем регистре ("sphere", а не "SPHERE"). Для скриптовых утилит убедитесь, что shape указан в верхнем регистре ("CONE", а не "cone"). Также проверьте, что origin или Target действительно разрешаются в допустимую локацию.

Проверьте также фильтр сущностей и мир. Нативные запросы зон босса по умолчанию используют living, исключают босса и ищут в его мире. Зоны скриптовых утилит по умолчанию используют PLAYER. Получайте цели Lua-зоны через full_target() или border_target() объекта зоны; у отдельного context.script:target({targetType = "ZONE_FULL"}) нет связанной зоны.

7. Частицы не появляются​

Используйте допустимое имя Bukkit Particle: и "FLAME", и "flame" приводятся к верхнему регистру. Проверьте позицию, количество и необходимые данные. Для BLOCK нужны данные блока, которые этот помощник мира не передаёт; используйте поддерживаемую частицу или API частиц скрипта с параметром материала.

8. Перезарядка, похоже, не работает​

check_local(key, duration) проверяет и устанавливает перезарядку за один вызов. Можно также вызвать local_ready(key), а затем set_local(duration, key), когда атака действительно начнётся. Одна проверка готовности не запускает перезарядку. Явные ключи общие для способностей одного босса, поэтому используйте префикс конкретной способности. Метаданные YAML не ограничивают выполнение Lua-хуков автоматически.

9. Босс продолжает выполнять способность после смерти​

Задачи, принадлежащие context.scheduler, отменяются при закрытии среды выполнения способности. Отменяйте циклы атаки в on_exit_combat, если они должны остановиться до удаления босса. Проверьте и вызванный вспомогательный метод: некоторые Java-методы запускают собственные задачи. Например, world:run_fake_gold_nugget_damage(...) уже запускает цикл каждый тик со своим таймаутом; вызывайте его только один раз на группу снарядов.


Чтение сообщений об ошибках​

При ошибке выполнения Lua-способности консоль выводит блок с префиксом [Lua], указывая файл и хук или колбэк. Номер строки появляется, если его можно извлечь из исходной ошибки. Перед отладкой читайте сообщение целиком. В файлах с -- @include номера строк относятся к развёрнутому исходному коду.

Типичная ошибка выглядит так:

[Lua] Error in 'push_zone.lua' at line 35 during 'on_boss_damaged_by_player':
[Lua] -> You tried to call a method or function that doesn't exist.
[Lua] -> Check the method name for typos, or make sure you're using ':' (colon) for method calls, not '.' (dot).
[Lua] -> Script has been disabled for this entity to prevent further errors.

Система переводит распространённые ошибки Lua на простой язык. Вот самые частые из них:

Сырая ошибка LuaЧто говорит консоль
attempt to call nilВы попытались вызвать несуществующий метод или функцию. Проверьте имя метода на опечатки или убедитесь, что используете : (двоеточие) для вызовов методов, а не . (точку).
index expected, got nilВы попытались обратиться к полю чего-то, что равно nil. Проверьте, что более ранний код инициализировал это.
attempt to indexВы попытались обратиться к свойству nil или недопустимого значения.
bad argumentПоказывает конкретные детали несоответствия аргумента (ожидаемый тип vs. фактический тип).
Превышен бюджет выполненияСообщение указывает предел инструкций, процессорного времени или резервного прошедшего времени; см. раздел о бюджете выше.
подсказка

Неисправный экземпляр скрипта отключается для этой сущности. После исправления файла нужно перезагрузить определение и создать новую среду выполнения; неизменённый отключённый экземпляр не повторяет сбойный хук каждый тик.


Не предполагайте существование недокументированных псевдонимов​

Lua API предоставляет определённый набор имён методов. Если вы пишете способности вручную или с помощью ИИ, не предполагайте, что существуют сокращённые или альтернативные имена. Следующие имена приведены как примеры тех, что не существуют и вызовут ошибки:

  • show_temporary_boss_bar() — используйте player:show_boss_bar(title, color, style, duration).
  • run_command_as_player() — используйте player:run_command(command).
  • em.location(...) — неправильное имя метода. Используйте em.create_location(x, y, z), либо context.boss:get_location() / context.player.current_location.
  • em.vector(...) — неправильное имя метода. Используйте em.create_vector(x, y, z) или простые таблицы {x=0, y=1, z=0}.
  • em.zone.sphere(...) — неправильное имя метода. Используйте em.zone.create_sphere_zone(radius) или таблицу определения зоны вроде {kind = "sphere", radius = 5, origin = location}.
  • entity:teleport_to(...) — используйте entity:teleport_to_location(location).
  • В обёртках сущностей, предназначенных для боссов EliteMobs, используйте entity:set_velocity_vector(vector). Общие обёртки MagmaCore также предоставляют set_velocity(...); проверьте, какую обёртку возвращает ваш контекст.
  • entity:set_facing(...) — используйте entity:face_direction_or_location(direction_or_location).

Проверьте справочник API используемой обёртки: Босс и сущности, Мир и окружение, Зоны и нацеливание или общий API. Похожие контексты могут предоставлять разные методы; правдоподобное имя псевдонима не делает его доступным.


Советы по миграции для авторов EliteScript​

Если вы уже пишете хорошие EliteScripts, самый простой способ изучить Lua-способности:

  1. Продолжайте думать в терминах событий, целей, зон, относительных векторов и частиц. Концепции те же — меняется только синтаксис. События EliteScript становятся именами хуков вроде on_spawn или on_boss_damaged_by_player. Цели и зоны передаются как таблицы в context.script с использованием тех же имён полей, что задокументированы на страницах Зоны EliteScript и Цели EliteScript.

  2. Перенесите поток управления в Lua. Случайные броски, общие вспомогательные функции, циклы, постоянное состояние (context.state) и планирование задач (context.scheduler) — это то, что Lua добавляет и что чистый EliteScript не может легко сделать. Начните с преобразования одной ветвящейся или условной способности в Lua, сохраняя всё остальное без изменений.

  3. Используйте context.script для выбора целей и геометрии зон. Его таблицы спецификаций используют настоящие парсеры EliteScript для targetType, shape, Target, Target2, FinalTarget, range, offset, relativeOffset, coverage и filter. Сверяйте имена полей и значения по умолчанию с документацией EliteScript, но прочитайте и ограничения объектов Lua. Статические объекты Lua не выполняют кэширование track: false действия YAML, а унаследованные данные действия отсутствуют. offset и relativeOffset применяются к определённым позициям, включая LOCATION, LOCATIONS и выбранные точки зон. Явно задавайте фиксированное начало, если область должна оставаться на месте.

  4. Учитывайте две системы зон. context.script:zone({shape = "SPHERE", ...}) использует движок EliteScript с перечислениями в верхнем регистре и таблицами Target. context.zones использует строчный kind и обычные позиции origin/destination. Смешивание этих форматов ключей может привести к пустой или неожиданной зоне, предупреждениям или ошибке выполнения.


Путь обучения для начинающих​

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

  1. Напишите файл только с api_version = 1 и on_spawn.
  2. Заставьте босса отправить сообщение или воспроизвести звук.
  3. Добавьте перезарядку с context.cooldowns.
  4. Добавьте один хук, вызываемый игроком, например on_boss_damaged_by_player.
  5. Добавьте одно отложенное действие с context.scheduler:run_after(...).
  6. Добавьте один простой запрос нативной Lua-зоны или один простой context.script:target(...).
  7. Только потом переходите к вращающимся атакам, конечным автоматам и многоэтапным механикам.

Каждый шаг строится на предыдущем, и вы можете тестировать на каждом этапе. Не пытайтесь написать многофазного босса в качестве своей первой Lua-способности.


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

  • Начало работы — структура файлов, хуки, пошаговый разбор первой способности, шаблоны для копирования
  • NPC-скрипты — скрипты близости, взаимодействия и жизненного цикла NPC
  • Примеры и паттерны — полностью рабочие способности для изучения и адаптации