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

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

Эта страница описывает типичные проблемы, с которыми вы можете столкнуться при написании или отладке скриптов пропсов и предметов FreeMinecraftModels, а также советы по работе с системой ленивой генерации конфигурации. Рабочие примеры см. в Примерах и паттернах. Если вы только начинаете, см. Начало работы.

Среда выполнения Lua MagmaCore

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


Типичные проблемы

1. Конфиг не загружается / Скрипт не привязан

Симптом: Пропс появляется, но не реагирует на клики и никакие хуки не срабатывают.

Причины и решения:

  • Файл .yml конфига ещё не существует. FMM генерирует конфиг лениво при первом спавне пропса. При первом спавне модели FMM создаёт файл .yml конфигурации асинхронно со значениями по умолчанию (включён, без скриптов). Вам нужно отредактировать сгенерированный конфиг, добавить имена файлов скриптов, а затем переспавнить пропс.

  • В конфиге isEnabled: false. Откройте файл .yml рядом с файлом модели и установите isEnabled: true.

  • Список scripts: пуст. Добавьте имена файлов ваших скриптов:

    isEnabled: true
    scripts:
    - my_script.lua
  • Имя файла .yml не совпадает с именем файла модели. Конфиг должен иметь то же базовое имя, что и файл модели. Например, для torch_01.fmmodel нужен torch_01.yml в той же директории.


2. Файл скрипта не найден

Симптом: Консоль показывает: [FMM Scripts] Script 'my_script.lua' not found in scripts/ folder

Причины и решения:

  • Неправильная директория. Файлы скриптов должны находиться в plugins/FreeMinecraftModels/scripts/, а не рядом с файлом модели.

  • Несовпадение имени файла. Имя в конфиге .yml должно точно совпадать с именем файла в папке scripts/, включая регистр и расширение .lua.

  • Файл отсутствует. Убедитесь, что файл .lua действительно существует в папке scripts/.


3. Скрипт вообще не загружается

Симптом: Нет ошибок в консоли, но хуки не срабатывают.

Проверьте консоль сервера на наличие синтаксических ошибок Lua при запуске. Наиболее частые причины:

  • Отсутствие end для закрытия функции или блока if
  • Непарные скобки
  • Отсутствие запятой между определениями хуков в возвращаемой таблице
  • Файл не заканчивается расширением .lua

Если есть ошибка Lua, консоль выведет блок ошибки [Lua] с именем файла, номером строки и описанием.


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

Симптом: Скрипт загружается (вы видите сообщение [FMM Scripts] Bound script), но конкретный хук не срабатывает.

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

Неправильное имяПравильное имя
on_clickon_right_click или on_left_click
on_interacton_right_click
on_hiton_left_click
on_tickon_game_tick
on_removeon_destroy
on_arrow_hiton_projectile_hit

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


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

Симптом: Консоль показывает ошибку Lua, поднятую изнутри VM. Вызов, вышедший за бюджет, прерывается посреди выполнения одним из сообщений:

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)

Каждый хук, коллбэк и вычисление файла могут выполнить не более 250 000 инструкций Lua либо израсходовать 50 мс процессорного времени текущего потока — что наступит раньше, — а вложенные вызовы разделяют одну квоту. (Третье сообщение появляется только на JVM, где замер процессорного времени по потокам недоступен; MagmaCore тогда использует более щедрое ограничение в 250 мс затраченного времени.) Скрипт выполнял слишком много работы за один вызов хука. Типичные причины:

  • Перебор слишком многих сущностей в on_game_tick
  • Создание слишком многих частиц за тик
  • Выполнение ресурсоёмких циклов без распределения работы по тикам

Решение: Перенесите тяжёлую работу за context.cooldowns или используйте scheduler:run_repeating() с разумным интервалом вместо on_game_tick.


6. context.event равен nil в хуке клика

В нормальных условиях это не должно происходить для on_left_click и on_right_click, но всегда проверяйте:

if context.event then
context.event.cancel()
end

Внутри обратных вызовов планировщика context.event всегда nil — это ожидаемое поведение. Модификация событий возможна только внутри самого хука события.


7. Анимация не воспроизводится

Симптом: play_animation() возвращает false, или ничего видимого не происходит.

Причины и решения:

  • Неправильное имя анимации. Имя должно точно совпадать с определённым в файле модели. Проверьте ваш .bbmodel или .fmmodel на правильное имя анимации.

  • Модель не имеет анимаций. Не все модели имеют анимации. Убедитесь, что файл модели действительно содержит данные анимации.

  • Игроки не в зоне действия ресурспака. Анимация серверная, но игрокам нужен загруженный ресурспак FMM, чтобы вообще видеть модель.


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

  • Убедитесь, что имя частицы — валидное значение перечисления Bukkit Particle в ВЕРХНЕМ_РЕГИСТРЕ: "FLAME", а не "flame".
  • Убедитесь, что местоположение находится в загруженном чанке. Если поблизости нет игроков, чанк может быть выгружен.
  • Убедитесь, что count не менее 1.
  • Некоторые частицы (например, DUST) требуют специальных дополнительных данных, которые базовый spawn_particle() может не поддерживать. Используйте стандартные частицы, такие как FLAME, HEART, HAPPY_VILLAGER, NOTE, ENCHANT и др.

9. Звук не воспроизводится

  • Убедитесь, что имя звука — валидная константа перечисления Bukkit Sound в ВЕРХНЕМ_РЕГИСТРЕ. Пример: "BLOCK_NOTE_BLOCK_HARP", а не "block.note_block.harp".
  • Убедитесь, что координаты местоположения корректны (не все нули и не NaN).
  • Убедитесь, что громкость больше 0.

10. Состояние неожиданно сбрасывается

context.state уникален для каждого экземпляра пропса и сохраняется на протяжении его жизни. Если состояние кажется сброшенным:

  • Пропс мог быть удалён и переспавнен (каждый спавн создаёт новый экземпляр).
  • Вы можете читать состояние в обратном вызове планировщика, используя неправильную переменную context. Всегда используйте собственный параметр context обратного вызова.

11. Библиотека os недоступна

Симптом: Скрипт падает с ошибкой вроде attempt to index nil (os).

Библиотека os полностью удалена из песочницы Lua. Вы не можете использовать os.time(), os.clock() или любую другую функцию os.

Решение: Используйте context.world:get_time() для времени мира либо отслеживайте прошедшее время вручную через флаги состояния и счётчики тиков в on_game_tick. Для перезарядок предпочтительнее context.cooldowns:check_local(key, ticks).


12. Скрипт предмета не активируется

Симптом: Пользовательский предмет находится в руке игрока, но ни один хук предмета не срабатывает.

Причины и решения:

  • Нет поля material: в YML-конфиге. Скрипты предметов работают только на моделях, у которых в конфиге задано material:. Без этого поля FMM считает модель реквизитом, а не пользовательским предметом.

  • Отсутствует PDC-ключ fmm_item_id. У предмета в инвентаре игрока должен быть ключ fmm_item_id в PersistentDataContainer. Предметы, полученные ванильными командами или сторонними плагинами, могут не иметь этой метки. Используйте /fmm giveitem <id> или админ-меню, чтобы получить корректно помеченный предмет.

  • Имя файла скрипта не указано в конфиге. Убедитесь, что в .yml-конфиге предмета имя файла скрипта присутствует в списке scripts:, так же как и у реквизита.


13. Скрипт предмета активируется и тут же деактивируется

Симптом: В консоли видно, как скрипт быстро привязывается и отвязывается, либо on_equip срабатывает и сразу за ним on_unequip.

Причина: Отслеживание экипировки предметов срабатывает по событиям смены слота. Если вы выдали себе предмет командой, и он попал в неактивный слот, либо ваш активный слот сменился во время выдачи, цикл equip/unequip может сработать в быстрой последовательности.

Решение: После выдачи себе предмета через /fmm giveitem <id> обязательно вручную переключитесь на слот инвентаря с этим предметом. Скрипт активируется на основании того, какой слот выбран в данный момент, а не просто по наличию предмета в инвентаре.


14. Хук срабатывает, но скрипт падает на методах сущности

Симптом: Хук вроде on_shift_right_click работает, но скрипт падает внутри запланированного коллбэка с ошибками вроде attempt to call nil при вызове entity:damage() или entity:push().

Причина: context.world:get_nearby_entities() возвращает ВСЕ сущности в радиусе, включая неживые (армор-стенды, выброшенные предметы, сферы опыта, области эффектов). У этих сущностей нет методов живых сущностей вроде damage(), push() или add_potion_effect().

Решение: Всегда проверяйте if entity.damage then перед вызовом методов живых сущностей:

local entities = context.world:get_nearby_entities(x, y, z, radius)
for _, entity in ipairs(entities) do
if entity.damage then
-- Безопасно вызывать методы живых сущностей
entity:damage(5.0)
entity:push(0, 0.5, 0)
end
end

15. Данные, доступные только в событии, отсутствуют в запланированных коллбэках

Симптом: Данные из события клика/боя отсутствуют внутри коллбэка scheduler:run_later() или scheduler:run_repeating().

Причина: Запланированные коллбэки получают свежий контекст и не сохраняют исходное Bukkit-событие или актора общей зоны. context.event в коллбэках всегда nil. Скрипты предметов всё ещё могут получить context.player от владельца предмета, пока скрипт жив, но скрипты реквизита получают context.player верхнего уровня только во время хуков, вызванных игроком, — таких как клики и вход/выход из зоны. Скриптам реквизита следует захватывать игрока из context.player или context.event.player во время исходного хука, если он понадобится позже.

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

on_right_click = function(context)
local clicked_player = context.player or (context.event and context.event.player)

context.scheduler:run_later(20, function(later_context)
-- Скрипты предметов могут использовать здесь later_context.player.
-- Скриптам реквизита следует использовать захваченного игрока, если нужен кликнувший.
local player = later_context.player or clicked_player
if player and player.is_valid then
player:send_message("&aDelayed message!")
end
end)
end

Как работает ленивая генерация конфигурации

Понимание системы ленивой генерации конфигурации помогает избежать путаницы при настройке новых пропсов:

  1. Первый спавн: Когда пропс появляется и не существует соседнего файла .yml, FMM создаёт конфиг асинхронно. Это означает, что файл записывается в фоновом потоке и не доступен немедленно.

  2. Значения по умолчанию: Сгенерированный конфиг имеет isEnabled: true и пустой список scripts: [].

  3. Нет скриптов при первом спавне: Поскольку конфиг создаётся после того, как пропс уже заспавнился (и не имеет перечисленных скриптов), при первом спавне у пропса не будет привязанных скриптов.

  4. Редактирование и переспавн: После того как FMM создаст конфиг, вы редактируете его, добавляя имена файлов скриптов. При следующем спавне пропса скрипты будут загружены.

  5. Расположение конфига: Файл .yml создаётся в той же директории, что и файл модели, с тем же базовым именем. Например:

    • Модель: plugins/FreeMinecraftModels/models/fountain.fmmodel
    • Конфиг: plugins/FreeMinecraftModels/models/fountain.yml
Быстрый рабочий процесс настройки
  1. Поместите модель в models/
  2. Поместите скрипт в scripts/
  3. Заспавните пропс один раз (генерирует .yml)
  4. Отредактируйте .yml, добавив scripts: [my_script.lua]
  5. Переспавните пропс — скрипт теперь активен

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

Когда что-то идёт не так в Lua-скрипте пропса, консоль выводит блок ошибки [Lua]. Эти сообщения точно указывают какой файл, какая строка, какой хук и что пошло не так на понятном языке.

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

[Lua] Error in 'my_door.lua' at line 12 during 'on_right_click':
[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Вы попытались вызвать метод или функцию, которая не существует. Проверьте опечатки и использование : vs ..
index expected, got nilВы попытались обратиться к полю чего-то, что является nil. Проверьте, что предыдущий код его инициализировал.
attempt to indexВы попытались обратиться к свойству nil или невалидного значения.
bad argumentФункция получила аргумент неправильного типа. Сообщение показывает ожидаемый и фактический тип.
Превышен бюджетLua instruction budget exceeded (250000 instruction limit) или Lua CPU-time budget exceeded (50ms current-thread CPU limit) — вызов был прерван посреди выполнения, а экземпляр скрипта отключён.
подсказка

Всегда читайте полное сообщение об ошибке [Lua] перед тем, как погружаться в код. Обычно оно указывает прямо на исправление.


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

API Lua FMM предоставляет определённый набор методов. Не предполагайте, что существуют сокращённые или альтернативные имена. Типичные ошибки:

  • context.prop:get_location() — не существует. Используйте context.prop.current_location (поле, а не метод).
  • context.prop:set_animation("open") — не существует. Используйте context.prop:play_animation("open", true, true).
  • context.event:setCancelled(true) — не существует. Используйте context.event.cancel().
  • context.cooldowns:check_global(...) — не существует. Используйте context.cooldowns:global_ready() и context.cooldowns:set_global(ticks) для глобальных перезарядок.
  • context.player — задан только для тех хуков реквизита FMM, которые связаны с игроком, таких как хуки кликов и хуки входа/выхода из наблюдаемой зоны. Это не общее поле владельца реквизита, и в хуках жизненного цикла реквизита и в коллбэках планировщика он будет nil. Скрипты предметов определяют context.player по владельцу предмета.
  • context.boss — не существует. Это из EliteMobs. Используйте context.prop в FMM.

Если сомневаетесь, проверьте страницу Prop API. Если чего-то нет в документации — этого не существует.


Советы по отладке

1. Используйте context.log:info() активно

Добавляйте сообщения лога на каждом шаге при отладке:

on_right_click = function(context)
context.log:info("Right click received!")
context.log:info("Event present: " .. tostring(context.event ~= nil))
context.log:info("State is_open: " .. tostring(context.state.is_open))
end

2. Проверяйте консоль при запуске

FMM логирует [FMM Scripts] Bound script 'X' to prop 'Y' для каждой успешной привязки. Если вы не видите это сообщение, конфиг или скрипт не загрузился.

3. Проверяйте путь к файлу модели

Конфиг .yml должен быть соседом файла модели (та же директория, то же базовое имя). Проверьте это:

  • Путь к файлу модели: plugins/FreeMinecraftModels/models/my_model.fmmodel
  • Путь к файлу конфига: plugins/FreeMinecraftModels/models/my_model.yml

4. Тестируйте хуки по отдельности

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

5. Проверяйте опечатки в именах хуков

Самая частая причина, по которой хук не срабатывает — это неправильно написанное имя хука. Скрипт загрузится без ошибок, но неправильно написанная функция хука будет отклонена при валидации.


Путь прогрессии для начинающих

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

  1. Напишите файл только с api_version = 1 и on_spawn, который логирует сообщение.
  2. Добавьте скрипт в конфиг пропса и убедитесь, что сообщение лога появляется.
  3. Добавьте on_right_click и логируйте, когда по пропсу кликают.
  4. Добавьте on_left_click с context.event.cancel() для неуязвимости.
  5. Воспроизведите анимацию по правому клику.
  6. Добавьте звук по правому клику.
  7. Добавьте состояние и поведение переключения.
  8. Добавьте наблюдение за зоной для обнаружения приближения.
  9. Только после этого переходите к сложным скриптам с множеством хуков и планировщиками.

Каждый шаг строится на предыдущем, и вы можете тестировать на каждом этапе.


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