Lua-скриптинг: Примеры и паттерны
Здесь приведены полные примеры Lua-способностей EliteMobs с пояснениями. Сохраните каждый пример в отдельный файл .lua, назначьте его тестовому боссу, выполните /em reload и после правок создайте нового босса. Анимации slam, roar, frenzy и transform должны существовать в пользовательской модели босса: это не встроенные анимации. Босс без пользовательской модели пропускает эти вызовы. Время ниже рассчитано для 20 TPS.
Если вы новичок в Lua-способностях, начните с Начала работы. Для NPC см. Скрипты NPC. Подробности API способностей босса см. в Справочнике API, Босс и сущности, Мир и окружение, Зоны и нацеливание и Перечисления.
Пример: Зонное нацеливание со скриптовыми утилитами
Чему учит этот пример: Как использовать context.script для создания геометрии зон в стиле EliteScript из Lua, генерации частиц и нанесения урона сущностям в зоне.
Полный файл способности (нажмите для раскрытия)
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
local cone = context.script:zone({
shape = "CONE",
Target = {
targetType = "SELF",
offset = "0,1,0"
},
Target2 = {
targetType = "NEARBY_PLAYERS",
range = 20
},
radius = 5
})
context.script:spawn_particles(
cone:full_target(0.4),
{
particle = "FLAME",
amount = 1,
speed = 0.05
}
)
context.script:damage(cone:full_target(), 1.0, 1.5)
end
}
Пояснение
-
Создание зоны --
context.script:zone(...)использует те же имена полей, что и Зоны EliteScript.Targetзадаёт вершину конуса на один блок выше босса. Первая разрешённая позицияTarget2определяет центр основания и длину конуса;radius = 5задаёт радиус основания.NEARBY_PLAYERSищет в прямоугольной области, простирающейся на 20 блоков по каждой оси, и не гарантирует ближайшего игрока. Чтобы нацелиться на атакующего в этом хуке урона, используйтеDIRECT_TARGET. Если игрок не найден, конуса нет; концы должны быть разными точками одного мира. -
Генерация частиц --
cone:full_target(0.4)возвращает дескриптор цели, который при каждом вызове случайно выбирает точки зоны с вероятностью 40%. Это не гарантирует точное количество и не фильтрует получателей урона. Настройки частиц используют поля EliteScriptparticle,amountиspeed. Утилита смещает выбранные позиции зоны на полблока по X и Z, поэтому частицы не обозначают точные границы попадания по сущностям. -
Урон --
context.script:damage(cone:full_target(), 1.0, 1.5)поражает разрешённых игроков внутри конусов, поскольку фильтр зоны по умолчанию —PLAYER. Первое число (1.0) — передаваемый базовый урон, второе (1.5) — множитель урона EliteMobs для игроков. Итоговый урон определяет обработка боя.
Пример выполняется при каждом переданном событии удара: локальной и глобальной перезарядки нет, как и задержки предупреждения между частицами и уроном. При сочетании с другими атаками добавьте явные проверки перезарядки. Каждый запрос статической зоны заново разрешает цели; Lua-дескриптор зоны не применяет автоматически условия YAML-действий или кэширование целей.
Таблицы зон и целей, передаваемые в context.script, используют имена полей EliteScript (targetType, shape, Target, Target2, range, offset, coverage). Полный список см. в Зонах EliteScript и Целях EliteScript.
Пример: Цикл атак с состоянием и планировщиком
Чему учит этот пример: Использование context.state для отслеживания состояния выполнения, context.scheduler для повторяющихся задач и правильный жизненный цикл входа/выхода из боя.
Полный файл способности (нажмите для раскрытия)
local function pick_action(context)
local roll = math.random(1, 2)
if roll == 1 then
context.boss:play_model_animation("slam")
else
context.boss:play_model_animation("roar")
end
end
return {
api_version = 1,
on_spawn = function(context)
context.state.started = false
context.state.loop_task_id = nil
end,
on_enter_combat = function(context)
if context.state.started then
return
end
context.state.started = true
context.state.loop_task_id = context.scheduler:run_every(100, function(loop_context)
if loop_context.boss.exists then
pick_action(loop_context)
end
end)
end,
on_exit_combat = function(context)
if context.state.loop_task_id ~= nil then
context.scheduler:cancel_task(context.state.loop_task_id)
context.state.loop_task_id = nil
end
context.state.started = false
end
}
Пояснение
- Инициализация состояния --
on_spawnустанавливаетcontext.state.startedвfalse, аcontext.state.loop_task_idвnil. Таблицаstateсохраняется между хуками этого рантайма способности на этом боссе. У других способностей отдельные таблицы состояния. - Защита боя --
on_enter_combatпроверяетcontext.state.startedперед запуском цикла, предотвращая множественные перекрывающиеся циклы. - Паттерн планировщика --
context.scheduler:run_every(100, callback)планирует первый вызов без задержки, затем повторяет его каждые 100 тиков (5 секунд). Перед первым вызовом он не ждёт пять секунд. Если нужна такая задержка, используйтеrun_repeating(100, 100, callback). Колбэк получает свежий контекст без исходного события или игрока, поэтомуloop_context.boss— свежий снимок. Сохраните возвращённый числовой ID задачи в состоянии. Пример только выбирает анимацию и сам по себе не наносит урон. - Очистка при выходе --
on_exit_combatотменяет повторяющуюся задачу по сохранённому ID и сбрасывает состояние. Без этой очистки планировщик продолжает работать после окончания боя.
Отменяйте задачи, нужные только в бою, в on_exit_combat. Без очистки они остаются в расписании после боя до отмены или закрытия рантайма способности. Приостановленный рантайм пропускает колбэки, сохраняя задачу. Необработанная ошибка колбэка закрывает рантайм и отменяет принадлежащие ему задачи.
Пример: Эффект огня при ударе
Чему учит этот пример: Простая способность «при ударе применить эффект» — самый распространённый паттерн для боевых способностей.
Полный файл способности (нажмите для раскрытия)
return {
api_version = 1,
on_player_damaged_by_boss = function(context)
-- Guard: the player may be nil in edge cases
if context.player == nil then
return
end
if not context.cooldowns:global_ready() then
return
end
-- Check and set a 60-tick (3 second) local cooldown in one call
if not context.cooldowns:check_local("fire_touch", 60) then
return
end
-- Coordinate with other powers that check the shared cooldown
context.cooldowns:set_global(40)
-- Set the player on fire for 60 ticks (3 seconds)
context.player:set_fire_ticks(60)
-- Visual feedback: spawn flame particles at the player's location
context.world:spawn_particle_at_location(
context.player.current_location,
{ particle = "FLAME", amount = 20, speed = 0.1 }
)
-- Tell the player what happened
context.player:send_message("&cThe boss's touch burns!")
end
}
Пояснение
- Защита от nil --
context.playerразрешается из игрока-участника текущего события или его прямой цели. Проверяйте его перед использованием, особенно при переносе кода в другой хук. Колбэки планировщика не наследуют этого игрока. - Локальная перезарядка -- После проверки
global_ready()вызовcontext.cooldowns:check_local("fire_touch", 60)проверяет ключ и сразу задаёт перезарядку на 60 тиков, если он готов. Иначе возвращаетfalse, и хук завершается. Способности на одном боссе разделяют этот ключ; разные боссы независимы. При объединении примеров добавляйте к ключам префикс имени способности. - Время горения --
context.player:set_fire_ticks(60)задаёт Bukkit длительность горения 60 тиков (3 секунды), проверив, что игрок действителен, жив, онлайн, находится в мире источника и, внутри экземпляра подземелья, является текущим участником. Время заменяется, а не увеличивается на 60 тиков. Другие игровые механики могут погасить огонь; сообщение и частицы не доказывают, что эффект был принят. - Частицы --
context.world:spawn_particle_at_location(location, spec)создаёт частицы в указанной позиции. Таблица настроек принимаетparticle(имя перечисления частиц Bukkit),amountиspeed. - Сообщение --
context.player:send_message(text)отправляет цветное сообщение в чат. Стандартные цветовые коды Minecraft, например&c(красный), обрабатываются автоматически. - Глобальная перезарядка --
context.cooldowns:set_global(40)задаёт общую перезарядку босса на 40 тиков (2 секунды) до применения эффектов. Пример проверяет её до расходования локальной перезарядки. Другие Lua-хуки и колбэки тоже должны вызыватьglobal_ready(), если им следует ждать: установка глобальной перезарядки не приостанавливает все Lua-колбэки.
Пример: Зонная AoE-способность с нативными Lua-зонами
Чему учит этот пример: Создание и запрос нативных Lua-зон для нанесения урона игрокам в области.
Полный файл способности (нажмите для раскрытия)
return {
api_version = 1,
on_spawn = function(context)
context.state.aoe_task_id = nil
end,
on_enter_combat = function(context)
-- Prevent duplicate loops
if context.state.aoe_task_id ~= nil then
return
end
context.state.aoe_task_id = context.scheduler:run_every(60, function(tick_context)
-- Make sure the boss is still alive
if not tick_context.boss.exists then
return
end
if not tick_context.cooldowns:global_ready() then
return
end
-- Check and set this boss's named cooldown before applying effects
if not tick_context.cooldowns:check_local("pulse_aoe", 60) then
return
end
tick_context.cooldowns:set_global(60)
-- Build a sphere zone centered on the boss's current position
local zone_def = {
kind = "sphere",
radius = 8,
origin = tick_context.boss:get_location()
}
-- Find all players inside the sphere
local victims = tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" })
-- Damage and show particles on each player found
for i = 1, #victims do
local victim = victims[i]
victim:deal_custom_damage(4.0)
tick_context.world:spawn_particle_at_location(
victim.current_location,
{ particle = "DUST", amount = 15, speed = 0, red = 128, green = 0, blue = 255 }
)
end
-- Spawn a particle burst at the boss; this does not draw the sphere
tick_context.world:spawn_particle_at_location(
tick_context.boss:get_location(),
{ particle = "WITCH", amount = 40, speed = 0.1 }
)
end)
end,
on_exit_combat = function(context)
if context.state.aoe_task_id ~= nil then
context.scheduler:cancel_task(context.state.aoe_task_id)
context.state.aoe_task_id = nil
end
end
}
Пояснение
- Настройка состояния --
on_spawnинициализируетaoe_task_idкакnil. Здесь будет храниться ID повторяющейся задачи планировщика. - Повторяющаяся атака --
on_enter_combatзапускает задачу без начальной задержки с интервалом 60 тиков (3 секунды). Проверка предотвращает дублирование циклов. Каждый колбэк проверяет глобальную перезарядку, затем проверяет и устанавливаетpulse_aoe, после чего задаёт глобальную перезарядку на 60 тиков до эффектов. Это координирует только способности, проверяющие эти ключи, и не блокирует автоматически все скрипты. - Определение зоны --
zone_defиспользует синтаксис нативных Lua-зон.kindзадаёт форму ("sphere"),radius— размер, аoriginполучает позицию босса на момент выполнения колбэка. Поэтому зона следует за движущимся боссом. - Запрос сущностей --
tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" })возвращает Lua-массив с индексами от 1, используя пересечение формы с сущностью, а не точную проверку расстояния до ног. Поиск идёт в текущем мире босса и не фильтрует игроков по режиму игры или участию в бою. Фильтры принимают"players","elites","mobs"или"living"(по умолчанию); сам босс исключён. - Нанесение урона --
victim:deal_custom_damage(4.0)передаёт 4 базового урона от босса черезBossCustomAttackDamage. Неуязвимые и мёртвые цели, а также игроки вне режимов Survival и Adventure пропускаются. В пути элитного моба к игроку вызов обходит обычную формулу урона и снижение от блокирования, а ванильное снижение от брони обнуляется. Обработчики урона, отмена события и поглощение всё ещё могут изменить потерю здоровья. Утилита сбрасывает нативную перезарядку урона цели после отправки; это другой путь, чемcontext.script:damage. - Частицы -- Фиолетовая пыль появляется в сохранённой позиции каждого найденного игрока, даже если урон отклонён. На боссе создаётся всплеск
WITCH, а не кольцо или граница сферы. В этих компактных таблицах частиц разброс позиции по умолчанию нулевой, а размер пыли фиксирован на1. - Очистка --
on_exit_combatотменяет повторяющуюся задачу и очищает состояние, как в предыдущем примере.
Этот пример использует нативные Lua-зоны (context.zones:get_entities_in_zone()), принимающие простую таблицу с kind, radius, origin и другими полями. Скриптовые утилиты (context.script:zone(...)) используют имена полей EliteScript, например shape, Target и Target2. Для простых форм используйте нативные зоны, для расширенного разрешения целей — context.script.
Пример: Многофазная механика босса
Чему учит этот пример: Использование состояния для отслеживания фаз босса и смены поведения при пороговых значениях здоровья.
Это фазы состояния Lua, а не YAML phases: EliteMobs, заменяющие определение босса. Здесь босс не заменяется и не исцеляется. Последующее лечение не возвращает его в фазу 1; выход из боя сохраняет фазу 2 до пересоздания рантайма способности.
Полный файл способности (нажмите для раскрытия)
local function phase_one_attack(context)
-- One damage pulse per callback; the animation does not delay it
context.boss:play_model_animation("slam")
local zone_def = {
kind = "sphere",
radius = 5,
origin = context.boss:get_location()
}
local targets = context.zones:get_entities_in_zone(zone_def, { filter = "players" })
for i = 1, #targets do
targets[i]:deal_custom_damage(3.0)
end
context.world:spawn_particle_at_location(
context.boss:get_location(),
{ particle = "EXPLOSION", amount = 3, speed = 0 }
)
end
local function phase_two_attack(context)
-- One pulse per callback, repeated more frequently in phase 2
context.boss:play_model_animation("frenzy")
local zone_def = {
kind = "sphere",
radius = 8,
origin = context.boss:get_location()
}
local targets = context.zones:get_entities_in_zone(zone_def, { filter = "players" })
for i = 1, #targets do
targets[i]:deal_custom_damage(2.0)
targets[i]:apply_potion_effect("SLOWNESS", 40, 1)
end
context.world:spawn_particle_at_location(
context.boss:get_location(),
{ particle = "DUST", amount = 30, speed = 0.2, red = 255, green = 0, blue = 0 }
)
context.world:play_sound_at_location(
context.boss:get_location(),
"entity.wither.ambient",
1.0, 1.5
)
end
local function start_attack_loop(context)
if context.state.attack_task_id ~= nil then return end
local phase = context.state.phase or 1
local interval = phase == 2 and 40 or 100
local attack = phase == 2 and phase_two_attack or phase_one_attack
context.state.attack_task_id = context.scheduler:run_every(interval, function(tick_context)
if not tick_context.boss.exists or not tick_context.boss.is_in_combat then return end
if tick_context.cooldowns:check_local("phase_attack", interval) then
attack(tick_context)
end
end)
end
return {
api_version = 1,
on_spawn = function(context)
context.state.phase = 1
context.state.attack_task_id = nil
end,
on_enter_combat = function(context)
start_attack_loop(context)
end,
on_game_tick = function(context)
-- Only check phase transition every 20 ticks (1 second) to stay lightweight
if not context.boss.is_in_combat then return end
if not context.cooldowns:check_local("phase_check", 20) then
return
end
-- Skip if already in phase 2
if context.state.phase ~= 1 then
return
end
-- Check health ratio
local health_ratio = context.boss.health / context.boss.maximum_health
if health_ratio <= 0.5 then
-- Transition to phase 2
context.state.phase = 2
context.log:info("Boss entering phase 2 at " .. tostring(math.floor(health_ratio * 100)) .. "% health")
-- Cancel the old attack loop
if context.state.attack_task_id ~= nil then
context.scheduler:cancel_task(context.state.attack_task_id)
context.state.attack_task_id = nil
end
-- Play transition effects
context.boss:play_model_animation("transform")
-- Announce the phase change to nearby players
local nearby = context.players.nearby_players(40)
for i = 1, #nearby do
nearby[i]:send_message("&4&lThe boss enters a frenzy!")
nearby[i]:show_title("&4Phase 2", "&cThe boss is enraged!", 10, 40, 10)
end
start_attack_loop(context)
end
end,
on_exit_combat = function(context)
if context.state.attack_task_id ~= nil then
context.scheduler:cancel_task(context.state.attack_task_id)
context.state.attack_task_id = nil
end
end
}
Пояснение
- Инициализация состояния --
on_spawnустанавливаетphaseв1, аattack_task_idвnil. Состояние принадлежит этому рантайму способности, не разделяется с другими способностями и не сохраняется между перезагрузками. - Цикл атак --
on_enter_combatвозобновляет текущую фазу черезstart_attack_loop: интервал фазы 1 — 100 тиков, фазы 2 — 40. В обоих случаях первый колбэк планируется без задержки. Для каждой атаки всё равно должна завершиться перезарядкаphase_attack; смена фазы не очищает старую перезарядку, поэтому колбэк может пропустить атаку. Пример не проверяет глобальную перезарядку. Фаза 1 передаёт 3 пользовательского урона игрокам в сфере радиусом 5 блоков и воспроизводит анимацию и частицы без ожидания длительности анимации. - Проверка фазы в on_game_tick -- Вне боя
on_game_tickсразу завершает работу, затем проверяет перезарядку в 20 тиков ("phase_check"). Если босс уже в фазе 2, обработчик также завершается досрочно. - Порог здоровья --
context.boss.health / context.boss.maximum_healthдаёт долю текущего здоровья. При 50% или ниже начинается переход. - Переход фазы -- Старый цикл отменяется, воспроизводится анимация трансформации, ближайшие игроки получают сообщение и заголовок, запускается новый более быстрый цикл (каждые 40 тиков).
- Атаки фазы 2 --
phase_two_attackиспользует сферу радиусом 8 блоков и передаёт один удар на 2 урона каждому найденному игроку за успешный колбэк. Он пытается применить замедление II на 40 тиков (amplifier = 1) с описанными выше проверками участия для эффектов, затем показывает красную пыль и воспроизводит звук визера. Несколько ударов внутри одного колбэка не планируются. - Логирование --
context.log:info(...)пишет в серверную консоль. - Очистка --
on_exit_combatотменяет любой активный цикл атак.
Активный, не приостановленный рантайм получает on_game_tick каждый серверный тик (20 раз в секунду при 20 TPS). Ограничивайте тяжёлые операции перезарядкой, например check_local("phase_check", 20). Здоровье проверяется периодически, поэтому смертельный удар может удалить босса до проверки перехода. Превышение бюджета выполнения закрывает этот рантайм способности и отменяет его задачи.
Советы по генерации с помощью ИИ
Для надёжной генерации Lua-способностей включите в промпт:
- Точное имя хука — например,
on_player_damaged_by_boss, а не «когда босс ударяет игрока». - Нативные Lua-зоны или скриптовые утилиты — укажите вариант. Нативные зоны используют
context.zones:get_entities_in_zone(zone_def, opts)сkind,radius,origin. Скриптовые утилиты используютcontext.script:zone(...)с полями EliteScript, такими какshape,Target,Target2. - Локальные и глобальные перезарядки — укажите ключ с префиксом способности и длительность в тиках.
context.cooldowns:check_local(key, ticks)проверяет и устанавливает именованную перезарядку босса. Для координации с глобальной перезарядкой явно проверяйтеglobal_ready()до расходования локального ключа и задавайте глобальную длительность до эффектов. - Имена анимаций пользовательской модели — например,
context.boss:play_model_animation("slam"). ИИ не может угадать имена анимаций; сообщите их. - Выбор цели — укажите
context.player(игрок события),context.players.nearby_players(range)или запрос зоны. - Тип эффекта — урон (
deal_custom_damage), зелье (apply_potion_effect), огонь (set_fire_ticks), скорость (set_velocity_vector,apply_push_vector) и т. д. - Методы правильной обёртки — предоставьте подходящий справочник API босса или общего API и не придумывайте псевдонимы.
- Поля EliteScript для скриптовых утилит —
targetType,shape,Target,Target2,range,offset,coverage, а не имена в стиле Lua. - Время и контекст планировщика — укажите, нужен ли первый колбэк сразу или с задержкой. У
run_everyначальная задержка нулевая;run_repeatingпринимает её отдельно. Используйте свежий контекст колбэка: в нём нет исходного игрока или события. Явно сохраните UUID игрока или позицию, если они понадобятся позже.
Хороший пример промпта
Напишите Lua-способность, которая в
on_enter_combatзапускает повторяющуюся задачу с начальной задержкой 80 тиков и интервалом 80 тиков. В каждом колбэке создайте нативную Lua-сферу радиусом 6 с центром на боссе, найдите игроков и передайте каждому 2.0 пользовательского урона. Проверьте глобальную перезарядку перед проверкой и установкой локального ключа"my_power:pulse"на 80 тиков, затем задайте глобальную перезарядку 80 до эффектов. Отмените задачу вon_exit_combat. Создайте частицы DUST (red=0, green=255, blue=100) у каждого найденного игрока.
Дополнительные ограничения, которые стоит включить
- «Верните одну таблицу с
api_version = 1.» - «Инициализируйте все поля состояния в
on_spawn.» - «Защищайте обратные вызовы планировщика через
if not tick_context.boss.exists then return end.» - «Отменяйте все повторяющиеся задачи в
on_exit_combat.» - «Не выдумывайте имена методов — используйте только методы со страницы справочника API.»
- «Используйте
context.cooldowns:check_local(key, ticks)для совмещённой проверки и установки.»
Чеклист QC
Проверьте Lua-способность по этому списку перед развёртыванием:
- Файл возвращает ровно одну таблицу с
api_version = 1. - Имена хуков точно соответствуют списку хуков, например
on_player_damaged_by_boss, а неon_player_hit. - В хуках, где игрок может отсутствовать,
context.playerпроверяется через== nilперед использованием. - Поля
context.stateинициализированы вon_spawn. - Каждая повторяющаяся задача, нужная только в бою, созданная через
run_everyилиrun_repeating, отменяется вon_exit_combat; её начальная задержка выбрана намеренно. - Колбэки планировщика используют собственный контекст и не предполагают наличие исходного игрока или события.
- Ключи перезарядки содержат префикс имени способности, длительности указаны в тиках. Глобальная координация включает явную проверку готовности.
- Хуки
on_game_tickзащищают тяжёлую работу проверкой перезарядки. - Все имена методов существуют в Справочнике API; выдуманных псевдонимов нет.
- Таблицы скриптовых утилит используют поля EliteScript (
targetType,shape,Targetи т. д.), а не имена в стиле Lua. - Нативные определения зон используют
kind,radius,origin,destinationи т.д. - Способность не вызывает блокирующих или длительных операций внутри хуков и колбэков.
- Настройки частиц используют допустимые имена перечислений Bukkit в UPPER_CASE, например
"FLAME","DUST","EXPLOSION". - Перезагрузите изменённые файлы, создайте нового босса и проверьте видимое поведение и консоль на тестовом сервере. Успешный разбор файла сам по себе не доказывает работоспособность атаки.
Лучшие практики
-
Начните с маленького хука и проверьте. Напишите один
on_spawn, отправляющий сообщение в лог. Подтвердите, что он срабатывает. Затем стройте дальше. -
Держите вспомогательные функции локальными. Объявляйте функции вроде
local function pick_action(context)над возвращаемой таблицей, чтобы зависимости оставались явными. Каждый экземпляр способности уже имеет собственный Lua-рантайм. -
Размещайте геометрию в скриптовых утилитах. Для конусов, вращающихся и перемещающихся лучей или анимированных зон используйте
context.script:zone(...)с полями EliteScript. Эти утилиты используют движок зон EliteScript. -
Используйте
context.stateдля состояния выполнения. Оно принадлежит одному рантайму способности на одном боссе и сохраняется между хуками этого рантайма. С другими способностями оно не разделяется и после перезагрузки не сохраняется. -
Используйте именованные локальные ключи перезарядки. Вместо простых чисел выбирайте понятные ключи вроде
"fire_touch"или"aoe_pulse". Явные ключи общие для всех способностей одного босса. Добавляйте префикс способности, например"my_power:fire_touch", чтобы избежать конфликтов. -
Держите
on_game_tickлёгким. Ограничивайте тяжёлые операции перезарядкой и оставляйте большой запас до бюджета выполнения. -
Отменяйте повторяющиеся задачи по завершении. Боевые циклы отменяйте в
on_exit_combat. Задачи рантайма автоматически отменяются при его закрытии, но после окончания боя продолжаются без явной отмены. -
Используйте свежие контексты колбэков планировщика.
run_every,run_repeatingиrun_afterпередают свежий контекст без события или игрока, запланировавшего вызов. Используйте этот аргумент и явно сохраняйте необходимые UUID или позиции: внешний контекст может содержать устаревшие снимки. -
Записывайте переходы состояния через
context.log:info(). Во время разработки записывайте смену фаз, начало перезарядок и запуск или остановку планировщика. Перед развёртыванием удалите частые сообщения:context.log:debug()босса тоже пишет на уровне info, только с другим префиксом. -
Используйте подходящие справочники EliteScript. Зоны, Цели и Относительные векторы описывают общие парсеры. YAML-условия, ожидания и кэширование на уровне действий не выполняются автоматически отдельными Lua-утилитами; выразите проверки в Lua. Наследуемые цели и позиции приземления требуют контекста YAML-действия, которого у этих дескрипторов нет.
Распространённые ошибки новичков
-
Использование внешнего
contextвнутри колбэка планировщика. Внешний контекст захватывает снимок на момент выполнения хука. Внутри колбэкаrun_everyилиrun_afterвсегда используйте собственный параметр колбэка (например,tick_context), который даёт вам свежий снимок. -
Забывают отменить повторяющиеся задачи. Если вы запускаете
run_everyвon_enter_combat, но никогда не отменяете, задача выполняется до тех пор, пока босс не будет удалён с сервера, даже после окончания боя. -
Не инициализируют состояние в
on_spawn. Если вы читаетеcontext.state.phaseвon_game_tick, но никогда не устанавливаете его вon_spawn, оно будетnilи ваши сравнения будут вести себя неожиданно. -
Используют
context.playerбез проверки nil. Уточняйте, какие хуки предоставляют игрока. В запланированных колбэках его по умолчанию нет; отсутствие проверки может закрыть рантайм способности. -
Используют имена полей в стиле Lua в скриптовых утилитах. Используйте документированные поля EliteScript:
targetType,Target,Target2,shape. Варианты с подчёркиваниями, такие какtarget_typeиzone_shape, не поддерживаются. Парсер зон принимает строчныеtargetиtarget2, но это не делает допустимыми произвольные псевдонимы в стиле Lua. -
Выполняют тяжёлую логику в
on_game_tickбез перезарядки. Поиск сущностей во всём мире и плотная геометрия частиц на множестве боссов дороги. Ограничивайте частоту этой работы и соблюдайте бюджет каждого колбэка. -
Смешивают API обёрток. Специальные обёртки боссов EliteMobs используют
entity:teleport_to_location(loc)иplayer:set_velocity_vector(vec). У общих обёрток MagmaCore есть дополнительные псевдонимы. Прежде чем брать метод из примера другого плагина, проверьте справочник собственного контекста. -
Используют
context.boss.healthдля установки здоровья.context.boss.health— это снимок только для чтения. Чтобы исцелить босса, используйтеcontext.boss:restore_health(amount). -
Забывают
api_version = 1. Возвращаемая таблица должна включать это поле, иначе EliteMobs не загрузит способность.
Следующие шаги
- Начало работы -- структура файлов, хуки, первая способность
- Скрипты NPC -- скрипты приближения и взаимодействия с NPC
- Хуки и жизненный цикл -- полный справочник хуков и контекста
- Босс и сущности -- методы босса, игрока и сущностей
- Мир и окружение -- частицы, звуки, молнии, блоки
- Зоны и нацеливание -- нативные зоны и зоны скриптовых утилит
- Перечисления -- допустимые значения для Particle, Sound, Material и других строковых констант
- Устранение неполадок -- распространённые проблемы, советы по отладке и миграции
