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

Lua-скриптинг: Зоны и нацеливание

webapp_banner.jpg

Lua-возможности EliteMobs предлагают два взаимодополняющих подхода для определения пространственных областей и разрешения целей:

  • Нативные зоны (context.zones) -- простые и прямые. Вы создаёте определение зоны как обычную Lua-таблицу и запрашиваете её на наличие сущностей или позиций. Лучше всего подходит для простых проверок «есть ли что-нибудь в этой области?».
  • Скриптовые утилиты (context.script) -- более богатое нацеливание, дескрипторы зон с методами watch/contains/entities, генерация частиц, нанесение урона и действия толчка. Используют те же имена полей, что и Зоны EliteScript и Цели EliteScript для привычности.

Оба подхода являются нативными Lua API. Выберите тот, который соответствует сложности вашей способности.


Нативные зоны: context.zones

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

Методы

МетодПримечания
zones:get_entities_in_zone(zoneDef, options)Возвращает массив обёрток сущностей внутри зоны
zones:get_locations_in_zone(zoneDef, options)Возвращает массив таблиц позиций внутри зоны
zones:zone_contains(zoneDef, location[, "full"|"border"])Возвращает true, если позиция находится внутри зоны
zones:watch_zone(zoneDef, callbacks, options)Регистрирует постоянный наблюдатель зоны, срабатывающий каждый тик

Поля определения зоны

ПолеТипПо умолчаниюПримечания
kind (или type)string"sphere", "dome", "cylinder", "cuboid", "cone", "static_ray", "rotating_ray", "translating_ray"
radiusnumber0Радиус зоны (sphere, dome, cylinder, cone)
heightnumber0Высота цилиндра
originlocationпозиция боссаЦентральная позиция
destinationlocationпозиция боссаКонечная точка для лучей и конусов
x, y, znumber0Полуразмеры параллелепипеда
thickness / point_radius / pointRadiusnumber0.5Толщина луча
border_radius / borderRadiusnumber1Ширина границы
x_border, y_border, z_bordernumber1Ширина границ параллелепипеда
animation_durationint0Длительность анимации в тиках для анимированных лучей
pitch_pre_rotation, yaw_pre_rotationnumber0Углы предварительного вращения (вращающийся луч)
pitch_rotation, yaw_rotationnumber0Углы вращения за тик (вращающийся луч)
origin_end, destination_endlocationпозиция боссаКонечные позиции для перемещающегося луча
ignores_solid_blocksbooleantrueПроходят ли лучи через твёрдые блоки
lengthnumber0Читается только при отсутствии destination; см. ниже.

Каждое многословное поле также принимает camelCase (borderRadius, xBorder, animationDuration, pitchPreRotation, ignoresSolidBlocks, ...), поэтому спецификация, скопированная из блока Zone: EliteScript, в основном работает как есть.

kind чувствителен к регистру

В отличие почти от любой другой строки в Lua API, kind сопоставляется точно и должен быть в нижнем регистре. "SPHERE" или "Sphere" ни с чем не совпадают, и зона молча разрешается в ничто — запросы просто возвращают пустой список без предупреждения в консоли.

Конусам и лучам нужен явный destination

И origin, и destination откатываются к собственной позиции босса, когда пропущены. Для конуса или луча это даёт фигуру нулевой длины, а не ошибку. Всегда задавайте destination для cone, static_ray, rotating_ray и translating_ray. Сокращения length не существует — задавайте явную локацию назначения.

Для конусов и лучей требуется destination или length

Если origin не указан, используется позиция босса. Если отсутствует destination, точка origin сдвигается на length блоков вдоль направления взгляда, сохранённого в таблице позиции origin.

Конус или луч можно задать двумя способами:

-- Explicit endpoint
local explicit_ray = { kind = "static_ray", origin = context.boss:get_eye_location(),
destination = context.player:get_eye_location() }

-- Or a length along the origin's own facing
local forward_ray = { kind = "static_ray", origin = context.boss:get_eye_location(), length = 15 }

Сокращение с length направлено верно только тогда, когда origin содержит yaw/pitch. Позиции из API (get_location(), get_eye_location(), current_location) их содержат. Таблица, написанная вручную, { x = .., y = .., z = .. } их не содержит; em.create_location(x, y, z) также требует необязательных аргументов yaw и pitch.

Если пропустить оба параметра, length по умолчанию равен 0: фигура имеет нулевую длину, а все запросы возвращают пустой результат без предупреждения.

Параметры запроса

КлючПримечания
filter"player" / "players", "elite" / "elites", "mob" / "mobs", "living" (по умолчанию)
mode"full" (по умолчанию) или "border"
coverageот 0.0 до 1.0 -- доля позиций для выборки (по умолчанию 1.0)

Колбэки наблюдения

КлючПримечания
on_enterfunction(entity) -- вызывается, когда сущность входит в зону
on_leavefunction(entity) -- вызывается, когда сущность покидает зону

Пример: базовый запрос сферы

Пример
return {
api_version = 1,
on_spawn = function(context)
-- Store a zone definition for reuse
context.state.danger_zone = {
kind = "sphere",
origin = context.boss:get_location(),
radius = 10
}
end,

on_boss_damaged_by_player = function(context)
-- Update the origin to the boss's current position
context.state.danger_zone.origin = context.boss:get_location()

local players = context.zones:get_entities_in_zone(
context.state.danger_zone,
{ filter = "players" }
)

for _, player in ipairs(players) do
player:send_message("&cYou are in the danger zone!")
end
end
}

Пример: наблюдение за зоной с входом/выходом

Пример
return {
api_version = 1,
on_spawn = function(context)
context.zones:watch_zone(
{
kind = "sphere",
origin = context.boss:get_location(),
radius = 8
},
{
on_enter = function(entity)
entity:apply_potion_effect("SLOWNESS", 40, 1)
entity:send_message("&7You feel sluggish near the boss...")
end,
on_leave = function(entity)
entity:send_message("&aYou escape the slowing aura.")
end
},
{ filter = "players", mode = "full" }
)
end
}

Примечания о наблюдателях:

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

Скриптовые утилиты: context.script

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

context.script запускает движок EliteScript

context.script — это не просто «именование в стиле EliteScript»: передаваемая вами таблица-спецификация преобразуется в обычную Java-карту и передаётся напрямую тем же классам целей, зон, относительных векторов и частиц, которые обеспечивают работу YAML-EliteScript. Отдельной реализации нет.

Что это значит на практике:

  • Каждое поле, описанное в Целях EliteScript, Зонах EliteScript и Относительных векторах EliteScript, работает в этих таблицах-спецификациях, включая любое поле, не перечисленное на этой странице.
  • Значения перечислений — точный UPPER_SNAKE_CASE ("NEARBY_PLAYERS", "SPHERE", "ZONE_FULL"), как и в YAML.
  • Имена полей — это имена из YAML, поэтому Target и Target2 пишутся с заглавной буквы, а targetType и borderRadius — в camelCase.
  • Поведение YAML на этапе загрузки тоже применяется: неразбираемое значение выводит предупреждение EliteScript и очищает поле, а не откатывается к значению по умолчанию.

Это противоположность context.zones, который является действительно отдельной облегчённой реализацией с именами kind в нижнем регистре.

Методы

МетодПримечания
script:target(spec)Создаёт дескриптор цели из таблицы спецификации цели
script:zone(spec)Создаёт дескриптор зоны из таблицы спецификации зоны
script:relative_vector(spec[, actionLocation][, zoneHandle])Создаёт дескриптор относительного вектора
script:damage(targetHandle, amount[, multiplier])Наносит урон разрешённым целям
script:push(targetHandle, vectorOrHandle[, additive])Толкает разрешённые цели
script:set_facing(targetHandle, vectorOrHandle)Устанавливает направление взгляда для целей
script:spawn_particles(targetHandle, particleSpec)Генерирует частицы в позициях разрешённых целей

Методы дескриптора цели

МетодПримечания
handle:entities()Возвращает массив обёрток сущностей
handle:locations()Возвращает массив таблиц позиций
handle:first_entity()Возвращает первую сущность или nil
handle:first_location()Возвращает первую позицию или nil

Ключи спецификации цели

КлючПо умолчаниюПримечания
targetType"SELF"Любой тип цели EliteScript
range20Дальность для ближних типов целей
coverage1.0от 0.0 до 1.0. Учитывается только для типов целей зоны; для любого другого типа сбрасывается в 1.0 с предупреждением в консоли
offset0,0,0Строка "x,y,z" или таблица { x = n, y = n, z = n }
relativeOffsetнетТаблица-спецификация относительного вектора, для смещения относительно направления взгляда босса
locationнетОдна локация, для "LOCATION"
locationsнетСписок локаций, для "LOCATIONS"
tracktrueНужно ли повторно разрешать движущиеся цели

Принимаются все 17 типов целей EliteScript, а не только распространённые: SELF, SELF_SPAWN, DIRECT_TARGET, NEARBY_PLAYERS, NEARBY_MOBS, NEARBY_ELITES, WORLD_PLAYERS, ALL_PLAYERS, LOCATION, LOCATIONS, ZONE_FULL, ZONE_BORDER, LANDING_LOCATION, ACTION_TARGET, INHERIT_SCRIPT_TARGET, INHERIT_SCRIPT_ZONE_FULL, INHERIT_SCRIPT_ZONE_BORDER. Типы ACTION_TARGET и INHERIT_* разрешаются во что-либо только при наличии окружающего контекста EliteScript, поэтому из Lua они малополезны.

Пример: создание и использование цели

Пример
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if not context.cooldowns:check_local("roar", 200) then return end
-- Find all players within 20 blocks
local nearby = context.script:target({
targetType = "NEARBY_PLAYERS",
range = 20
})

for _, player in ipairs(nearby:entities()) do
player:send_message("&eThe boss roars in fury!")
end

-- Single-entity access
local closest = nearby:first_entity()
if closest then
closest:show_title("&cRUN!", "&7The boss is targeting you")
end
end
}

Методы дескриптора зоны

МетодПримечания
handle:full_target([coverage])Возвращает дескриптор цели для полного объёма зоны
handle:border_target([coverage])Возвращает дескриптор цели для границы зоны
handle:full_locations([coverage])Возвращает позиции в полном объёме зоны
handle:border_locations([coverage])Возвращает позиции на границе зоны
handle:full_entities()Возвращает сущности в полном объёме зоны
handle:border_entities()Возвращает сущности на границе зоны
handle:contains(location[, "full"|"border"])Возвращает true, если позиция находится внутри зоны
handle:watch(callbacks[, mode])Наблюдает за зоной на предмет событий входа/выхода, возвращает ID задачи

Ключи спецификации зоны

КлючПо умолчаниюПримечания
shape"CYLINDER""SPHERE", "DOME", "CYLINDER", "CUBOID", "CONE", "STATIC_RAY", "ROTATING_RAY", "TRANSLATING_RAY"
radius5Sphere, dome, cylinder, cone
height1Только cylinder
x, y, z0Полуразмеры параллелепипеда
xBorder, yBorder, zBorder0Ширина границ параллелепипеда
borderRadius1Ширина границы для sphere / dome / cylinder / cone
pointRadius0.5Толщина луча
animationDuration0Длительность анимации в тиках для анимированных лучей
Target{targetType = "SELF"}Спецификация центральной цели (таблица) -- использует тот же формат спецификации цели
Target2нетВторая точка. Обязательна для конусов и всех форм лучей
FinalTarget, FinalTarget2нетКонечные позиции, только для перемещающегося луча
filter"PLAYER""PLAYER", "ELITE", "LIVING" — учтите, что здесь они в ВЕРХНЕМ регистре, в отличие от context.zones
ignoresSolidBlockstrueТолько лучи
pitchPreRotation, yawPreRotation0Только вращающийся луч
pitchRotation, yawRotation0Только вращающийся луч

Ключи, не относящиеся к выбранной shape, читаются без нареканий и затем игнорируются — height у сферы ничего не делает.

Пример: зона с уроном и частицами

Пример
return {
api_version = 1,
on_enter_combat = function(context)
-- Create a sphere zone centered on the boss
if context.state.zone_task_id ~= nil then return end
local zone = context.script:zone({
shape = "SPHERE",
radius = 6,
Target = { targetType = "SELF" }
})

-- Spawn warning particles on the zone border
context.script:spawn_particles(
zone:border_target(0.3),
{ particle = "FLAME", amount = 1, speed = 0.02 }
)

-- Damage all players inside the zone
local targets = zone:full_target()
context.script:damage(targets, 5.0)

-- Repeat every 20 ticks
context.state.zone_task_id = context.scheduler:run_every(20, function(ctx)
local z = ctx.script:zone({
shape = "SPHERE",
radius = 6,
Target = { targetType = "SELF" }
})

ctx.script:spawn_particles(
z:border_target(0.3),
{ particle = "FLAME", amount = 1, speed = 0.02 }
)

ctx.script:damage(z:full_target(), 5.0)
end)
end,
on_exit_combat = function(context)
if context.state.zone_task_id ~= nil then
context.scheduler:cancel_task(context.state.zone_task_id)
context.state.zone_task_id = nil
end
end
}

Ключи спецификации относительного вектора

КлючПримечания
SourceTargetСпецификация исходной цели (таблица)
DestinationTargetСпецификация целевой цели (таблица)
normalizeboolean -- нужно ли нормализовать результирующий вектор
multiplierКоэффициент масштабирования, применяемый после нормализации
offsetСтрока "x,y,z" или таблица { x = n, y = n, z = n }

Методы дескриптора относительного вектора

МетодПримечания
handle:resolve()Возвращает вычисленную таблицу вектора

Пример: толкание целей с помощью относительного вектора

Пример
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if not context.cooldowns:check_local("knockback", 100) then return end

-- Build a vector from the boss toward the attacker
local vec = context.script:relative_vector({
SourceTarget = { targetType = "SELF" },
DestinationTarget = { targetType = "DIRECT_TARGET" },
normalize = true,
multiplier = 2.5
})

-- Push the attacker away
local target = context.script:target({
targetType = "DIRECT_TARGET"
})

context.script:push(target, vec)
end
}

Формат спецификации частиц

Спецификации частиц могут быть строкой, одной таблицей или массивом таблиц. Поскольку этот путь запускает движок частиц EliteScript, ключи здесь — те же, что описаны в SPAWN_PARTICLE:

КлючПо умолчаниюПримечания
particle"FLAME"Название частицы (напр. "FLAME", "DUST", "SMOKE")
amount1Количество частиц. 0 превращает x/y/z в вектор скорости
x, y, z0.01Значения смещения/разброса или скорость, когда amount равен 0
speed0.01Скорость частиц
red, green, blue255Цвет для DUST, DUST_COLOR_TRANSITION, WITCH и других частиц с цветовыми данными (0-255)
toRed, toGreen, toBlue255Конечный цвет перехода для DUST_COLOR_TRANSITION
material"STONE"Блок или предмет, отображаемый частицами, которые несут данные блока/предмета (BLOCK, ITEM, FALLING_DUST, ...)
relativeVectorнетСпецификация относительного вектора. Её задание принудительно устанавливает amount в 0 и перезаписывает x/y/z разрешённым направлением
Два разных набора ключей частиц

Эти ключи EliteScript не совпадают с теми, что принимает context.world:spawn_particle_at_location(loc, spec). У таблицы world собственный, более скромный обработчик с другими значениями по умолчанию (amount 1, x/y/z/speed 0), без material и без relativeVector, и он дополнительно принимает to_red / to_green / to_blue в snake_case. См. Мир и окружение.

Полный список названий частиц см. в Справочнике перечислений.

Важные предупреждения

  • Дескрипторы скриптовых утилит привязаны к контексту события, в котором они были созданы. Не сохраняйте их в context.state для использования в последующем вызове хука.
  • watch() зоны возвращает ID задачи, который можно отменить с помощью context.scheduler:cancel_task().
  • Значения coverage применяются только к разрешению на основе позиций, а не к запросам сущностей.
  • Все строковые значения используют те же имена перечислений в UPPER_SNAKE_CASE, что и EliteScript YAML (напр. "SELF", "NEARBY_PLAYERS", "SPHERE").

Вспомогательное пространство имён em

Пространство имён em доступно глобально во всех файлах Lua-способностей. Оно предоставляет удобные конструкторы для позиций, векторов и определений зон.

Конструкторы позиций и векторов

ФункцияПримечания
em.create_location(x, y, z[, world][, yaw][, pitch])Возвращает таблицу позиции с методом add(dx, dy, dz)
em.create_vector(x, y, z)Возвращает таблицу вектора

Вспомогательные функции построения зон

Подтаблица em.zone предоставляет функции-конструкторы, возвращающие таблицы определения зон, совместимые с context.zones. Каждый конструктор возвращает таблицу с цепочечными методами-мутаторами.

ФункцияПараметрыМутаторы
em.zone.create_sphere_zone(radius)radius:set_center(location)
em.zone.create_dome_zone(radius)radius:set_center(location)
em.zone.create_cylinder_zone(radius, height)radius, height:set_center(location)
em.zone.create_cuboid_zone(x, y, z)x, y, z (полуразмеры):set_center(location)
em.zone.create_cone_zone(length, radius)length, radius:set_origin(location), :set_destination(location)
em.zone.create_static_ray_zone(length, thickness)length, thickness:set_origin(location), :set_destination(location)
em.zone.create_rotating_ray_zone(length, point_radius, animation_duration)length, point_radius, animation_duration:set_origin(location), :set_destination(location)
em.zone.create_translating_ray_zone(length, point_radius, animation_duration)length, point_radius, animation_duration:set_origin(location), :set_destination(location)
Аргумент length не используется

Конструкторы конусов и лучей сохраняют length в таблице спецификации, но context.zones его никогда не читает — фигура строится от origin до destination. Всегда вызывайте по цепочке :set_origin(...) и :set_destination(...) у этих конструкторов; иначе оба по умолчанию берут позицию босса, и фигура получается нулевой длины.

Пример

Пример
return {
api_version = 1,
on_spawn = function(context)
-- Create a location offset from the boss
local boss_loc = context.boss:get_location()
local above = em.create_location(boss_loc.x, boss_loc.y + 5, boss_loc.z)

-- Create a sphere zone using the builder
local zone = em.zone.create_sphere_zone(10):set_center(boss_loc)

-- Use with native zone queries
local players = context.zones:get_entities_in_zone(zone, { filter = "players" })
for _, p in ipairs(players) do
p:send_message("&cYou are within the boss's aura!")
end

-- Create a directional vector
local push_vec = em.create_vector(0, 1.5, 0)
context.boss:set_velocity_vector(push_vec)
end
}
к сведению

Пространство имён em не является экземплярным -- все экземпляры Lua-способностей разделяют одни и те же вспомогательные функции em. Функции являются чистыми конструкторами и не хранят состояния.


Нативные зоны vs. Скриптовые утилиты

Обе системы работают с одной и той же базовой геометрией зон. Вот когда использовать каждую:

Сценарий использованияРекомендуемый подход
Простая проверка «есть ли что-нибудь в этой области?»Нативные зоны (context.zones)
Быстрый запрос сущностей по формеНативные зоны
NEARBY_PLAYERS, ZONE_FULL с coverageСкриптовые утилиты (context.script)
Анимированные зоны (вращающиеся/перемещающиеся лучи)Оба -- нативные зоны тоже поддерживают эти формы
Дескрипторы зон с методами watch/contains/entitiesСкриптовые утилиты
Генерация частиц в позициях зоныСкриптовые утилиты (spawn_particles)
Действия урона и толчка, привязанные к нацеливаниюСкриптовые утилиты (damage, push)
Относительные векторы для направленных эффектовСкриптовые утилиты (relative_vector)
Сочетание потока управления Lua с богатым нацеливаниемСкриптовые утилиты

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


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