Skip to main content

Lua Scripting: Zones & Targeting

webapp_banner.jpg

EliteMobs Lua powers offer two complementary approaches for defining spatial areas and resolving targets:

  • Native zones (context.zones) -- simple and direct. You build a zone definition as a plain Lua table and query it for entities or locations. Best for straightforward "is anything in this area?" checks.
  • Script utilities (context.script) -- richer targeting, zone handles with watch/contains/entities methods, particle spawning, damage, and push actions. Uses the same field names as EliteScript Zones and EliteScript Targets for familiarity.

Both approaches are native Lua APIs. Pick whichever fits the complexity of your power.


Native Zones: context.zones

Native zones let you define zones as plain Lua tables and query them directly. No handles, no extra abstraction -- just a table describing a shape and a method call to query it.

Methods

MethodNotes
zones:get_entities_in_zone(zoneDef, options)Returns an array of entity wrappers inside the zone
zones:get_locations_in_zone(zoneDef, options)Returns an array of location tables inside the zone
zones:zone_contains(zoneDef, location[, "full"|"border"])Returns true if the location is inside the zone
zones:watch_zone(zoneDef, callbacks, options)Registers a persistent zone watcher that fires per tick

Zone definition fields

FieldTypeDefaultNotes
kind (or type)string"sphere", "dome", "cylinder", "cuboid", "cone", "static_ray", "rotating_ray", "translating_ray"
radiusnumber0Zone radius (sphere, dome, cylinder, cone)
heightnumber0Cylinder height
originlocationboss locationCenter location
destinationlocationboss locationEnd point for rays and cones
x, y, znumber0Cuboid half-extents
thickness / point_radius / pointRadiusnumber0.5Ray thickness
border_radius / borderRadiusnumber1Border width
x_border, y_border, z_bordernumber1Cuboid border widths
animation_durationint0Animation length in ticks for animated rays
pitch_pre_rotation, yaw_pre_rotationnumber0Pre-rotation angles (rotating ray)
pitch_rotation, yaw_rotationnumber0Per-tick rotation angles (rotating ray)
origin_end, destination_endlocationboss locationEnd positions for translating ray
ignores_solid_blocksbooleantrueWhether rays pass through solid blocks
lengthnumber0Only read when destination is omitted -- see below

Every multi-word field also accepts camelCase (borderRadius, xBorder, animationDuration, pitchPreRotation, ignoresSolidBlocks, ...), so a spec copied from an EliteScript Zone: block mostly works as-is.

kind is case-sensitive

Unlike almost every other string in the Lua API, kind is matched exactly and must be lowercase. "SPHERE" or "Sphere" do not match anything, and the zone silently resolves to nothing -- queries just return an empty list with no console warning.

Cones and rays need a destination -- or a length

origin falls back to the boss's own location when omitted. destination falls back to origin projected length blocks along the facing direction stored in the origin location table.

That gives you two working ways to build a cone or ray:

-- 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 }

The length shorthand only aims somewhere useful when the origin table carries yaw/pitch. Location tables returned by the API (get_location(), get_eye_location(), current_location) do; a hand-written { x = .., y = .., z = .. } table does not, and neither does em.create_location(x, y, z) unless you pass its optional yaw and pitch.

Omit both and length defaults to 0, so the shape collapses to zero length and every query silently returns nothing.

Query options

KeyNotes
filter"player" / "players", "elite" / "elites", "mob" / "mobs", "living" (default)
mode"full" (default) or "border"
coverage0.0 to 1.0 -- fraction of locations to sample (default 1.0)

Watch callbacks

KeyNotes
on_enterfunction(entity) -- called when an entity enters the zone
on_leavefunction(entity) -- called when an entity leaves the zone

Example: basic sphere query

Example
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
}

Example: zone watching with enter/leave

Example
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
}

Watcher notes:

  • Watcher callbacks receive a single entity wrapper directly, not via context.event
  • Watchers call their own on_enter / on_leave callbacks; they do not invoke the power's top-level on_zone_enter / on_zone_leave hooks
  • Watchers are cleaned up automatically when the boss is removed
  • Each watcher runs every tick, so keep callback logic lightweight
  • The boss entity itself is excluded from zone queries

Script Utilities: context.script

The script utilities provide target resolution, zone handles, relative vectors, particle spawning, and combat actions.

context.script runs the EliteScript engine

context.script is not merely "EliteScript-flavoured naming" -- the spec table you pass is converted to a plain Java map and handed straight to the same target, zone, relative-vector and particle classes that power YAML EliteScripts. There is no separate implementation.

What that means in practice:

  • Every field documented on EliteScript Targets, EliteScript Zones and EliteScript Relative Vectors works in these spec tables, including any field this page does not list.
  • Enum values are exact UPPER_SNAKE_CASE ("NEARBY_PLAYERS", "SPHERE", "ZONE_FULL"), the same as YAML.
  • Field names are the YAML names, so Target and Target2 are capitalised while targetType and borderRadius are camelCase.
  • YAML load-time behaviour applies too: an unparseable value logs an EliteScript warning and clears the field rather than falling back to its default.

This is the opposite of context.zones, which is a genuinely separate lightweight implementation using lowercase kind names.

Methods

MethodNotes
script:target(spec)Creates a target handle from a target spec table
script:zone(spec)Creates a zone handle from a zone spec table
script:relative_vector(spec[, actionLocation][, zoneHandle])Creates a relative-vector handle
script:damage(targetHandle, amount[, multiplier])Deals damage to resolved targets
script:push(targetHandle, vectorOrHandle[, additive])Pushes resolved targets
script:set_facing(targetHandle, vectorOrHandle)Sets facing direction for targets
script:spawn_particles(targetHandle, particleSpec)Spawns particles at resolved target locations

Target handle methods

MethodNotes
handle:entities()Returns array of entity wrappers
handle:locations()Returns array of location tables
handle:first_entity()Returns first entity or nil
handle:first_location()Returns first location or nil

Target spec keys

KeyDefaultNotes
targetType"SELF"Any EliteScript target type
range20Range for nearby-type targets
coverage1.00.0 to 1.0. Only honoured for zone target types; on any other type it is reset to 1.0 with a console warning
offset0,0,0"x,y,z" string or { x = n, y = n, z = n } table
relativeOffsetnoneA relative-vector spec table, for an offset relative to the boss's facing
locationnoneSingle location, for "LOCATION"
locationsnoneLocation list, for "LOCATIONS"
tracktrueWhether to re-resolve moving targets

All 17 EliteScript target types are accepted, not just the common ones: 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. The ACTION_TARGET and INHERIT_* types only resolve to anything when the surrounding EliteScript context exists, so they are of little use from Lua.

Example: creating and using a target

Example
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
}

Zone handle methods

MethodNotes
handle:full_target([coverage])Returns a target handle for the full zone volume
handle:border_target([coverage])Returns a target handle for the zone border
handle:full_locations([coverage])Returns locations in the full zone
handle:border_locations([coverage])Returns locations on the zone border
handle:full_entities()Returns entities in the full zone
handle:border_entities()Returns entities on the zone border
handle:contains(location[, "full"|"border"])Returns true if the location is inside the zone
handle:watch(callbacks[, mode])Watches zone for enter/leave events, returns task ID

Zone spec keys

KeyDefaultNotes
shape"CYLINDER""SPHERE", "DOME", "CYLINDER", "CUBOID", "CONE", "STATIC_RAY", "ROTATING_RAY", "TRANSLATING_RAY"
radius5Sphere, dome, cylinder, cone
height1Cylinder only
x, y, z0Cuboid half-extents
xBorder, yBorder, zBorder0Cuboid border widths
borderRadius1Border width for sphere / dome / cylinder / cone
pointRadius0.5Ray thickness
animationDuration0Animation length in ticks for animated rays
Target{targetType = "SELF"}Center target spec (table) -- uses the same target spec format
Target2noneSecond point. Required for cones and all ray shapes
FinalTarget, FinalTarget2noneEnd positions, translating ray only
filter"PLAYER""PLAYER", "ELITE", "LIVING" -- note these are UPPERCASE here, unlike context.zones
ignoresSolidBlockstrueRays only
pitchPreRotation, yawPreRotation0Rotating ray only
pitchRotation, yawRotation0Rotating ray only

Keys that do not apply to the chosen shape are read without complaint and then ignored -- a height on a sphere does nothing.

Example: zone with damage and particles

Example
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
}

Relative-vector spec keys

KeyNotes
SourceTargetSource target spec (table)
DestinationTargetDestination target spec (table)
normalizeboolean -- whether to normalize the resulting vector
multiplierScale factor applied after normalization
offset"x,y,z" string or { x = n, y = n, z = n } table

Relative-vector handle methods

MethodNotes
handle:resolve()Returns the computed vector table

Example: pushing targets with a relative vector

Example
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
}

Particle spec format

Particle specs can be a string, a single table, or an array of tables. Because this path runs the EliteScript particle engine, the keys are the EliteScript ones documented under SPAWN_PARTICLE:

KeyDefaultNotes
particle"FLAME"Particle name (e.g. "FLAME", "DUST", "SMOKE")
amount1Number of particles. 0 turns x/y/z into a velocity vector
x, y, z0.01Offset/spread values, or velocity when amount is 0
speed0.01Particle speed
red, green, blue255Colour for DUST, DUST_COLOR_TRANSITION, WITCH and other colour-data particles (0-255)
toRed, toGreen, toBlue255Transition target colour for DUST_COLOR_TRANSITION
material"STONE"Block or item shown by particles that carry block/item data (BLOCK, ITEM, FALLING_DUST, ...)
relativeVectornoneA relative-vector spec. Setting it forces amount to 0 and overwrites x/y/z with the resolved direction
Two different particle key sets

These EliteScript keys are not the same as the ones context.world:spawn_particle_at_location(loc, spec) accepts. The world table has its own smaller reader with different defaults (amount 1, x/y/z/speed 0), no material and no relativeVector, and it additionally accepts to_red / to_green / to_blue in snake_case. See World & Environment.

For the full list of particle names, see the Enum Reference.

Important caveats

  • Script utility handles are tied to the event context in which they were created. Do not store them in context.state for use in a later hook call.
  • context.script:zone(...):watch(...) returns a task ID that you can cancel with context.scheduler:cancel_task(). context.zones:watch_zone(...) returns nothing and is cleaned up automatically with the power.
  • Coverage values apply only to location-based resolution, not entity queries.
  • All string values use the same UPPER_SNAKE_CASE enum names as EliteScript YAML (e.g. "SELF", "NEARBY_PLAYERS", "SPHERE").

em Helper Namespace

The em namespace is available globally in all Lua power files. It provides convenience constructors for locations, vectors, and zone definitions.

Location and vector constructors

FunctionNotes
em.create_location(x, y, z[, world][, yaw][, pitch])Returns a location table with an add(dx, dy, dz) method
em.create_vector(x, y, z)Returns a vector table

Zone builder helpers

The em.zone sub-table provides builder functions that return zone definition tables compatible with context.zones. Each builder returns a table with chainable mutator methods.

FunctionParametersMutators
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 (half-extents):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 is only a fallback

The cone and ray builders store length in the spec table, and context.zones reads it only when no destination is set -- the endpoint then becomes the origin projected length blocks along the origin's own facing direction.

So either chain :set_origin(...) and :set_destination(...), or chain :set_origin(...) with a location that carries yaw/pitch (such as context.boss:get_eye_location()) and let length do the work. Chaining neither leaves both at the boss's location with length unusable, and the shape has zero length.

Example

Example
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
}
info

The em namespace is not per-instance -- all Lua power instances share the same em helper functions. The functions are pure constructors and do not carry any state.


Native Zones vs Script Utilities

Both systems work with the same underlying zone geometry. Here is when to use each:

Use caseRecommended approach
Simple "is anything in this area?" checkNative zones (context.zones)
Quick entity query with a shapeNative zones
NEARBY_PLAYERS, ZONE_FULL with coverageScript utilities (context.script)
Animated zones (rotating/translating rays)Either -- native zones support these shapes too
Zone handles with watch/contains/entities methodsScript utilities
Particle spawning at zone locationsScript utilities (spawn_particles)
Damage and push actions tied to targetingScript utilities (damage, push)
Relative vectors for directional effectsScript utilities (relative_vector)
Combining Lua control flow with rich targetingScript utilities

In practice, many powers use both. Native zones are great for the initial "are players nearby?" check in a cooldown guard, while script utilities handle the complex attack logic that follows.


Next Steps

  • Examples & Patterns -- complete working powers you can study and adapt
  • NPC Scripts -- NPC proximity hooks for simple approach/leave behavior
  • Enum Reference -- valid values for Particle, Sound, Material, and other string constants