Lua-скриптинг: Устранение неполадок
Эта страница описывает типичные проблемы, с которыми вы можете столкнуться при написании или отладке скриптов пропсов и предметов FreeMinecraftModels, а также советы по работе с системой ленивой генерации конфигурации. Рабочие примеры см. в Примерах и паттернах. Если вы только начинаете, см. Начало работы.
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_click | on_right_click или on_left_click |
on_interact | on_right_click |
on_hit | on_left_click |
on_tick | on_game_tick |
on_remove | on_destroy |
on_arrow_hit | on_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
Как работает ленивая генерация конфигурации
Понимание системы ленивой генерации конфигурации помогает избежать путаницы при настройке новых пропсов:
-
Первый спавн: Когда пропс появляется и не существует соседнего файла
.yml, FMM создаёт конфиг асинхронно. Это означает, что файл записывается в фоновом потоке и не доступен немедленно. -
Значения по умолчанию: Сгенерированный конфиг имеет
isEnabled: trueи пустой списокscripts: []. -
Нет скриптов при первом спавне: Поскольку конфиг создаётся после того, как пропс уже заспавнился (и не имеет перечисленных скриптов), при первом спавне у пропса не будет привязанных скриптов.
-
Редактирование и переспавн: После того как FMM создаст конфиг, вы редактируете его, добавляя имена файлов скриптов. При следующем спавне пропса скрипты будут загружены.
-
Расположение конфига: Файл
.ymlсоздаётся в той же директории, что и файл модели, с тем же базовым именем. Например:- Модель:
plugins/FreeMinecraftModels/models/fountain.fmmodel - Конфиг:
plugins/FreeMinecraftModels/models/fountain.yml
- Модель:
- Поместите модель в
models/ - Поместите скрипт в
scripts/ - Заспавните пропс один раз (генерирует
.yml) - Отредактируйте
.yml, добавивscripts: [my_script.lua] - Переспавните пропс — скрипт теперь активен
Чтение сообщений об ошибках
Когда что-то идёт не так в 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. Проверяйте опечатки в именах хуков
Самая частая причина, по которой хук не срабатывает — это неправильно написанное имя хука. Скрипт загрузится без ошибок, но неправильно написанная функция хука будет отклонена при валидации.
Путь прогрессии для начинающих
Если вы хотите изучить эту систему с нуля, такая прогрессия работает хорошо:
- Напишите файл только с
api_version = 1иon_spawn, который логирует сообщение. - Добавьте скрипт в конфиг пропса и убедитесь, что сообщение лога появляется.
- Добавьте
on_right_clickи логируйте, когда по пропсу кликают. - Добавьте
on_left_clickсcontext.event.cancel()для неуязвимости. - Воспроизведите анимацию по правому клику.
- Добавьте звук по правому клику.
- Добавьте состояние и поведение переключения.
- Добавьте наблюдение за зоной для обнаружения приближения.
- Только после этого переходите к сложным скриптам с множеством хуков и планировщиками.
Каждый шаг строится на предыдущем, и вы можете тестировать на каждом этапе.
Следующие шаги
- Начало работы — структура файлов, хуки, первый скрипт, шаблоны для копирования
- API пропсов и предметов — полный справочник API
- Примеры и паттерны — полные рабочие скрипты для изучения и адаптации