Lua Scripting: World & Environment
This page covers the EliteMobs boss extensions to context.world, plus context.vectors, context.settings, and context.event. These let Lua powers spawn entities, play effects, query blocks, and change world state. If you are new to Lua powers, start with Getting Started first.
EliteMobs boss powers reuse the MagmaCore Lua sandbox. Boss context.world starts from the shared MagmaCore world table, so generic coordinate helpers such as get_block_at(...), set_block_at(...), and spawn_particle(...) are still available. This page focuses on the EliteMobs boss-specific context.world, context.vectors, context.settings, and context.event additions and overrides. For the generic world API, see MagmaCore Lua Scripting Engine.
Where a same-named method exists in both, the EliteMobs version wins. The methods documented below keep EliteMobs semantics, including protection bypass on lightning, the richer particle spec, and the temporary-block tracker. Other shared methods remain available, including get_entity(uuid), block_is_passable(x, y, z), location_is_protected(location), spawn_dust(...), and raycast(...). Use the shared API reference for their arguments and unloaded-chunk behavior; those guarantees do not automatically apply to an EliteMobs override.
FreeMinecraftModels adds drop_elitemobs_procedural_loot, drop_elitemobs_random_loot, drop_elitemobs_custom_loot and start_elitemobs_transport to context.world in its own prop scripts only. Each plugin carries a separate copy of the shared scripting library, so these helpers do not appear in EliteMobs boss powers or NPC scripts, even with FreeMinecraftModels installed. Boss powers use generate_player_loot and drop_bonus_coins instead.
context.world
The world table provides methods for spawning entities, playing effects, modifying blocks, and changing world state. These methods accept colon syntax (world:method()) or dot syntax without an explicit self argument.
The boss overrides below convert a location table using x, y, z, optional yaw/pitch, and a loaded world name in world. Missing coordinates become 0; an omitted world uses the boss's current world, but an explicitly unknown world fails conversion. They also accept a nested current_location table. A nil or non-table location defaults to the boss location. Shared methods retain their separate location contract and the world captured when that table was created.
World changes and spawned entities are not automatically undone when the Lua instance closes. Script-owned monitors stop, but independent entity-removal timers, explosion regeneration, empowered lightning and gold-projectile tasks have their own lifetimes. Block and spawn overrides do not automatically apply location_is_protected or the shared unloaded-chunk guards; check the relevant constraints before changing the world.
world:spawn_particle_at_location(location, particleSpec)
Spawns particles at a location. The second argument can be a particle name string or a particle spec table. An optional third integer argument supplies the count when amount is absent; its default is 1. Names are uppercased before lookup. Invalid names or unsupported particle data produce a warning and no particle.
| Parameter | Type | Notes |
|---|---|---|
location | location table | Where to spawn particles |
particleSpec | string or table | Particle name or a particle spec table (see below) |
Example
return {
api_version = 1,
on_enter_combat = function(context)
-- Simple particle by name
context.world:spawn_particle_at_location(context.boss:get_location(), "FLAME")
-- Full particle spec table with red dust
context.world:spawn_particle_at_location(context.boss:get_location(), {
particle = "DUST",
amount = 10,
x = 0.5,
y = 0.5,
z = 0.5,
speed = 0.02,
red = 255,
green = 0,
blue = 0,
})
end
}
Particle Spec Table Format
| Key | Type | Default | Notes |
|---|---|---|---|
particle | string | required | Particle name |
amount | int | 1 | Number of particles |
x, y, z | number | 0 | Spread/offset in each axis |
speed | number | 0 | Particle speed |
red, green, blue | int | 255 | RGB color for DUST and DUST_COLOR_TRANSITION |
toRed, toGreen, toBlue | int | 255 | Transition target color for DUST_COLOR_TRANSITION |
For DUST_COLOR_TRANSITION, you can also use snake_case keys: to_red, to_green, to_blue. Camel-case values take precedence. Both dust types use a fixed size of 1; RGB components must be in 0..255. Other particles receive no extra data payload, so data-dependent particles are not all supported by this helper.
Unlike EliteScript YAML configs, Lua particle names do not support legacy particle name conversions. You must use the current Spigot API names (e.g., FIREWORK not FIREWORKS_SPARK, SMOKE not SMOKE_NORMAL, DUST not REDSTONE). See the valid particles list for the full list of current names and their legacy equivalents.
world:play_sound_at_location(location, sound, volume, pitch)
Plays a sound at a location.
| Parameter | Type | Default | Notes |
|---|---|---|---|
location | location table | Where to play the sound | |
sound | string | Minecraft sound key (e.g. "entity.blaze.shoot") or a Spigot Sound enum name. Resource-pack keys work too. | |
volume | number | 1.0 | Volume |
pitch | number | 1.0 | Pitch |
Example
return {
api_version = 1,
on_spawn = function(context)
context.world:play_sound_at_location(
context.boss:get_location(),
"entity.wither.spawn",
1.0,
0.5
)
end
}
world:strike_lightning_at_location(location)
Strikes real, damaging lightning at a location and enables EliteMobs' lightning-spawn bypass. This is not the visual-only shared lightning helper, and the bypass is not a guarantee that every other plugin's damage or protection rules are ignored.
| Parameter | Type | Notes |
|---|---|---|
location | location table | Where to strike |
Example
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then return end
if not context.cooldowns:check_local("lightning", 100) then return end
context.world:strike_lightning_at_location(context.player:get_location())
context.player:send_message("&eA bolt of lightning strikes you!")
end
}
world:spawn_entity_at_location(entityType, location, options)
Spawns a vanilla Minecraft entity directly in the location's world. Unknown entity names return nil; a recognized but non-spawnable type can still fail. This helper does not automatically set a projectile shooter or make the entity an elite reinforcement.
| Parameter | Type | Notes |
|---|---|---|
entityType | string | Entity type name (e.g. "FIREBALL", "ARROW") |
location | location table | Where to spawn |
options | table (optional) | Spawn options (see below) |
Entity Spawn Options
| Key | Type | Default | Notes |
|---|---|---|---|
velocity | vector table | nil | Initial velocity {x, y, z} |
effect | string | nil | EntityEffect to play on spawn |
duration | int | 0 | Positive values schedule removal after this many ticks; nonpositive values add no removal timer |
on_land | function | nil | function(land_location, entity_ref) when grounded, invalid, or past max_ticks; not a projectile-impact event and not proof the entity landed |
max_ticks | int | 6000 | Max ticks to monitor before forcing the on_land callback |
persistent | boolean | Minecraft's default | false keeps the entity out of the chunk save, so a shutdown or chunk unload mid-effect cannot leave it in the world |
Returns an entity wrapper table. Non-living entities (FIREBALL, ARROW, TNT, ...) come back as a
non-living reference wrapper where is_valid() is a method.
Living entity types (ZOMBIE, GIANT, ...) come back as a full
living-entity wrapper instead, where is_valid is a boolean field -- calling
entity:is_valid() on one of those raises attempt to call a boolean value.
The landing monitor polls every tick while the script is active and stops with its owned tasks. The positive duration timer is independent and can still remove the entity after the script closes. Unknown effect names are ignored.
Example
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then return end
if not context.cooldowns:check_local("fireball_spawn", 100) then return end
-- Spawn a fireball aimed at the player
local boss_loc = context.boss:get_location()
local dir = context.vectors.get_vector_between_locations(boss_loc, context.player:get_location())
dir = context.vectors.normalize_vector(dir)
context.world:spawn_entity_at_location("FIREBALL", boss_loc, {
velocity = { x = dir.x * 2, y = dir.y * 2, z = dir.z * 2 },
duration = 100,
})
end
}
Example
return {
api_version = 1,
on_enter_combat = function(context)
-- Spawn a primed TNT with a landing callback
context.world:spawn_entity_at_location("TNT", context.boss:get_location(), {
velocity = { x = 0, y = 1.5, z = 0 },
on_land = function(land_location, entity_ref)
context.world:spawn_particle_at_location(land_location, {
particle = "EXPLOSION_EMITTER",
amount = 1,
})
end,
})
end
}
world:spawn_display_at_location(location, options)
Spawns an item or block display: a floating visual with no hitbox, gravity or AI. Use it for orbiting stones, thrown props and other effects that only need to be seen, instead of styling an armor stand. The display is centered on location and turned by its yaw.
| Option | Type | Default | Notes |
|---|---|---|---|
item | string | Material shown as an item. A block item such as "AMETHYST_BLOCK" shows as a centered block | |
block | string | Block shown as a block display, as a material or block data such as "lantern[hanging=true]" | |
scale | number or vector table | 1 | A number scales every axis; {x, y, z} scales each. At 1 a block is one block wide |
duration | int | 0 | Positive values remove the display after this many ticks |
teleport_duration | int | 0 | Ticks the client takes to glide to each new location, 0 to 59. Use 1 when you teleport the display every tick |
billboard | string | FIXED | FIXED, VERTICAL, HORIZONTAL or CENTER |
glowing | boolean | false | Outline glow |
brightness | int | world light | 0 to 15, overriding the light at the display |
bedrock_fallback | boolean | true | See below |
Give exactly one of item or block. A missing or unknown item or block, both at once, or a scale that is not positive returns nil and logs a warning naming the power. An unknown billboard value keeps FIXED.
Displays are never saved with the chunk. They are removed when duration runs out, when you call remove(), or when the power shuts down, whichever comes first.
Bedrock clients cannot render display entities. Bedrock players who are in the world when the display spawns instead see an invisible armor stand wearing the same item, or the block's item for a block display, and the display is hidden from them. The stand follows the display and is removed with it. Bedrock players who enter the world later get no stand, and a block that has no item form, such as fire, gets no stand at all. Turn this off with bedrock_fallback = false for effects Bedrock players do not need to see.
The returned wrapper has uuid and entity_type and these methods:
| Method | Notes |
|---|---|
is_valid(), is_alive() | Whether the display still exists |
get_location() | Current location |
teleport_to_location(location) | Moves the display, and its Bedrock stand |
remove() | Removes the display and its Bedrock stand |
set_scale(scale) | Same values as the scale option |
set_item(material) | Item displays only; returns false otherwise |
set_block(block) | Block displays only; returns false otherwise |
set_glowing(enabled) | Outline glow |
set_brightness(level) | 0 to 15, or nil to use the world's light again |
set_teleport_duration(ticks) | Same as the teleport_duration option |
Example
-- Three amethyst stones circle the target for three seconds.
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil or not context.cooldowns:check_local("orbit", 200) then return end
local target = context.player
local stones = {}
for i = 1, 3 do
stones[i] = context.world:spawn_display_at_location(target:get_location(), {
item = "AMETHYST_BLOCK", scale = 0.3, duration = 60, teleport_duration = 1
})
end
local elapsed = 0
local task
task = context.scheduler:run_every(1, function(context)
elapsed = elapsed + 1
if elapsed >= 60 then
context.scheduler:cancel_task(task)
return
end
local center = target:get_location()
for i = 1, 3 do
local angle = elapsed * 0.2 + i * (2 * math.pi / 3)
if stones[i] ~= nil and stones[i]:is_valid() then
stones[i]:teleport_to_location(em.create_location(
center.x + math.cos(angle) * 1.5, center.y + 1.2, center.z + math.sin(angle) * 1.5, center.world, 0, 0))
end
end
end)
end
}
world:spawn_falling_block_at_location(location, material, options)
Spawns a falling block entity using a block material. With on_land, EliteMobs intercepts its block-change event, cancels placement, and calls function(land_location, entity_ref) once. Without that callback, normal falling-block placement can occur. This helper has no duration or max_ticks option and no invalid-entity/timeout callback.
| Parameter | Type | Notes |
|---|---|---|
location | location table | Where to spawn |
material | string | Material name (e.g. "STONE", "MAGMA_BLOCK") |
options | table (optional) | Spawn options (see below) |
Falling Block Options
| Key | Type | Default | Notes |
|---|---|---|---|
velocity | vector table | nil | Initial velocity {x, y, z} |
drop_item | boolean | false | Whether the block drops as an item |
hurt_entities | boolean | false | Whether the block damages entities it lands on |
on_land | function | nil | Callback function(land_location, entity_ref) when the block lands |
Returns a non-living entity reference table, or nil if the location or material cannot be resolved. A recognized non-block material can still fail when creating block data.
Example
return {
api_version = 1,
on_enter_combat = function(context)
-- Rain magma blocks from above
local loc = context.boss:get_location()
for i = 1, 5 do
local offset_loc = {
x = loc.x + math.random(-3, 3),
y = loc.y + 10,
z = loc.z + math.random(-3, 3),
world = loc.world
}
context.world:spawn_falling_block_at_location(offset_loc, "MAGMA_BLOCK", {
velocity = { x = 0, y = -0.5, z = 0 },
hurt_entities = true,
on_land = function(land_location, entity_ref)
context.world:spawn_particle_at_location(land_location, { particle = "LAVA", amount = 8 })
end,
})
end
end
}
world:spawn_custom_boss_at_location(filename, location, options)
Spawns an EliteMobs custom boss from a config file.
| Parameter | Type | Notes |
|---|---|---|
filename | string | Boss config filename (e.g. "my_boss.yml") |
location | location table | Where to spawn |
options | table (optional) | Spawn options (see below) |
Custom Boss Spawn Options
| Key | Type | Default | Notes |
|---|---|---|---|
level | int | spawned boss's config level | Override the spawned boss level. If omitted, its own config and dynamic-level rules apply; this does not inherit the casting boss's level. |
silent | boolean | false | Suppress spawn messages |
velocity | vector table | nil | Initial velocity |
add_as_reinforcement | boolean | false | Register as a reinforcement of the current boss |
Returns an entity wrapper table, or nil if the boss could not be created.
Example
return {
api_version = 1,
on_enter_combat = function(context)
local minion = context.world:spawn_custom_boss_at_location(
"skeleton_minion.yml",
context.boss:get_location(),
{ level = 10, add_as_reinforcement = true }
)
if minion ~= nil then
context.boss:send_message("&cMinions, arise!")
end
end
}
world:spawn_boss_at_location(filename, location, level)
A simpler boss spawn method. Omitted location and level use the casting boss's location and level. It does not register the result as a reinforcement. Omit the third argument to use that default; explicitly passing nil there is not accepted.
| Parameter | Type | Default | Notes |
|---|---|---|---|
filename | string | Boss config filename | |
location | location table | boss location | Where to spawn (optional, defaults to boss location) |
level | int | boss level | Override spawn level (optional) |
Returns an entity wrapper table, or nil.
Example
return {
api_version = 1,
on_enter_combat = function(context)
context.world:spawn_boss_at_location("zombie_guard.yml", context.boss:get_location(), 5)
context.boss:send_message("&cGuards, defend me!")
end
}
world:spawn_reinforcement_at_location(filename, location, duration, velocity)
Spawns silently through the EliteMobs reinforcement system, records the summoner, and inherits normalized combat when the custom-boss summoner uses it. The reinforcement's own configuration and dynamic-level rules determine its level; duration is an escape timer, not a level override.
| Parameter | Type | Default | Notes |
|---|---|---|---|
filename | string | Boss config filename | |
location | location table | Where to spawn | |
duration | int | 0 | Escape timer in ticks; 0 adds no timer. This argument does not override the reinforcement level. |
velocity | vector table | nil | Initial velocity (optional) |
Returns an entity wrapper table, or nil.
Example
return {
api_version = 1,
on_enter_combat = function(context)
context.world:spawn_reinforcement_at_location(
"necro_skeleton.yml",
context.boss:get_location(),
0,
{ x = 0, y = 0.5, z = 0 }
)
context.boss:send_message("&5Rise, my servant!")
end
}
world:spawn_fireworks_at_location(location, spec)
Spawns a real firework entity, with no automatic damage suppression. Provide at least one valid color per effect; an effect without colors can fail during construction.
| Parameter | Type | Notes |
|---|---|---|
location | location table | Where to spawn |
spec | table | Firework specification (see below) |
Firework Spec Table
| Key | Type | Default | Notes |
|---|---|---|---|
power | int | 1 | Flight power (height) |
velocity | vector table | nil | Initial velocity |
shot_at_angle | boolean | true | Whether the firework is shot at an angle (used when velocity is set) |
effects | table of tables | nil | Array of firework effect specs. If omitted, the top-level spec is treated as a single effect. |
Firework Effect Spec
| Key | Type | Default | Notes |
|---|---|---|---|
type | string | "BALL_LARGE" | "BALL", "BALL_LARGE", "STAR", "BURST", "CREEPER" |
flicker | boolean | true | Twinkle effect |
trail | boolean | true | Trail effect |
colors | table | required for each effect | Nonempty array of color names or {red = 255, green = 0, blue = 0} tables |
fade_colors | table | nil | Array of fade color names or {red, green, blue} tables |
Color names: "WHITE", "SILVER", "GRAY", "BLACK", "RED", "MAROON", "YELLOW", "OLIVE", "LIME", "GREEN", "AQUA", "TEAL", "BLUE", "NAVY", "FUCHSIA", "PURPLE", "ORANGE"
Returns a non-living entity reference table, or nil if the location cannot be resolved. Call :detonate() to request detonation. Type and named-color lookup ignore case; an unknown type falls back to BALL_LARGE and an unknown color to white. RGB values must be in 0..255.
Example
return {
api_version = 1,
on_spawn = function(context)
local fw = context.world:spawn_fireworks_at_location(context.boss:get_location(), {
power = 0,
effects = {
{
type = "STAR",
flicker = true,
trail = true,
colors = { "RED", "ORANGE", { red = 255, green = 200, blue = 0 } },
fade_colors = { "YELLOW" },
},
},
})
if fw == nil then return end
-- Detonate on the next tick for a visual burst
context.scheduler:run_after(1, function()
fw:detonate()
end)
end
}
world:spawn_splash_potion_at_location(location, spec)
Spawns a real thrown splash potion. Unknown Bukkit effect names are warned about and skipped. This helper does not set a shooter or apply the instance-effect filter used by direct boss potion methods; normal potion splash behavior applies.
| Parameter | Type | Notes |
|---|---|---|
location | location table | Where to spawn |
spec | table | Potion specification (see below) |
Splash Potion Spec Table
| Key | Type | Notes |
|---|---|---|
velocity | vector table | Initial velocity for the thrown potion |
effects | table of tables | Array of potion effect specs |
Potion Effect Spec
| Key | Type | Default | Notes |
|---|---|---|---|
type | string | required | Potion effect type name (e.g. "SLOWNESS", "POISON") |
duration | int | 0 | Duration in ticks |
amplifier | int | 0 | Effect amplifier (0 = level I) |
overwrite | boolean | true | Replace an earlier effect of the same type in this potion's metadata; not a rule for effects already on a player |
Returns an entity reference table, or nil.
Example
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then return end
if not context.cooldowns:check_local("potion_throw", 200) then return end
local dir = context.vectors.get_vector_between_locations(
context.boss:get_location(),
context.player:get_location()
)
dir = context.vectors.normalize_vector(dir)
context.world:spawn_splash_potion_at_location(context.boss:get_location(), {
velocity = { x = dir.x * 1.5, y = 0.5, z = dir.z * 1.5 },
effects = {
{ type = "SLOWNESS", duration = 100, amplifier = 1 },
{ type = "WEAKNESS", duration = 60, amplifier = 0 },
},
})
end
}
world:place_temporary_block_at_location(location, material, duration, requireAir)
Places a tracked block that restores its previous block data after a positive duration. This saves block data, not container contents. The original block comes back when the timer ends, when the chunk or world unloads, or when the plugin shuts down, but only while the block still holds the temporary block, so a later edit is not overwritten. Placing again on a block that still holds a temporary block restarts its timer and keeps the first saved original. Blocks with block entities, such as chests and signs, unloaded chunks, and blocks held by another temporary-block owner are skipped. Breaking a tracked block drops nothing and ends tracking without restoring. Use a block material. This boss override returns nil, including when requireAir or a skip prevents placement; it does not report success or check region protection automatically.
| Parameter | Type | Default | Notes |
|---|---|---|---|
location | location table | Where to place | |
material | string | Material name | |
duration | int | 0 | Ticks before restoration; 0 or a negative value places the block permanently without tracking |
requireAir | boolean | false | If true, only places if the block is currently air |
Example
return {
api_version = 1,
on_enter_combat = function(context)
-- Create a temporary ice floor under the boss
local loc = context.boss:get_location()
for dx = -2, 2 do
for dz = -2, 2 do
context.world:place_temporary_block_at_location(
{ x = loc.x + dx, y = loc.y - 1, z = loc.z + dz, world = loc.world },
"PACKED_ICE",
100,
false
)
end
end
end
}
world:set_block_at_location(location, material, requireAir)
Sets a block permanently through the temporary-block manager, with no restoration timer and no tracking, so the change stays after the plugin shuts down. If the block currently holds a timed temporary block, that block stops being tracked and is not restored. Blocks held by another temporary-block owner are left unchanged. Use a positive duration with place_temporary_block_at_location when the original block should be restored on a timer.
| Parameter | Type | Default | Notes |
|---|---|---|---|
location | location table | Where to place | |
material | string | Material name | |
requireAir | boolean | false | If true, only places if the block is currently air |
Example
return {
api_version = 1,
on_enter_combat = function(context)
-- Place fire at the boss's location
context.world:set_block_at_location(context.boss:get_location(), "FIRE", true)
end
}
world:get_block_type_at_location(location)
Returns the uppercase material name at a location, or nil if the location cannot be resolved. Unlike the shared coordinate query, this override accesses the block directly without an unloaded-chunk guard.
| Parameter | Type | Notes |
|---|---|---|
location | location table | Location to query |
Example
return {
api_version = 1,
on_enter_combat = function(context)
local block_type = context.world:get_block_type_at_location(context.boss:get_location())
if block_type == "WATER" then
context.boss:send_message("&bThe boss is empowered by water!")
context.boss:apply_potion_effect("SPEED", 200, 1)
end
end
}
world:get_highest_block_y_at_location(location)
Returns Bukkit's highest-block Y coordinate at the given X/Z position, or nil if the location cannot be resolved. It does not check whether the chunk is already loaded or whether the destination is safe for teleportation.
| Parameter | Type | Notes |
|---|---|---|
location | location table | Location to query (X and Z are used) |
Example
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then return end
if not context.cooldowns:check_local("skyport", 200) then return end
local loc = context.player:get_location()
local ground_y = context.world:get_highest_block_y_at_location(loc)
-- Teleport the player to the surface
context.player:teleport_to_location({
x = loc.x, y = ground_y + 1, z = loc.z, world = loc.world
})
context.player:send_message("&eYou are teleported to the surface!")
end
}
world:get_blast_resistance_at_location(location)
Returns the material's blast resistance at a location, or 0 when the location cannot be resolved.
| Parameter | Type | Notes |
|---|---|---|
location | location table | Location to query |
Example
return {
api_version = 1,
on_enter_combat = function(context)
local resistance = context.world:get_blast_resistance_at_location(context.boss:get_location())
context.log:info("Blast resistance at boss: " .. resistance)
end
}
world:is_air_at_location(location)
Returns true if the block at the location is air.
| Parameter | Type | Notes |
|---|---|---|
location | location table | Location to query |
Example
return {
api_version = 1,
on_enter_combat = function(context)
local loc = context.boss:get_location()
local above = { x = loc.x, y = loc.y + 2, z = loc.z, world = loc.world }
if context.world:is_air_at_location(above) then
-- There is space above the boss, launch upward
context.boss:set_velocity_vector({ x = 0, y = 1.5, z = 0 })
end
end
}
world:is_passable_at_location(location)
Returns Bukkit's Block.isPassable() result, or false for an unresolved location. This is a single-block query, not a check that a whole entity can fit or navigate there.
| Parameter | Type | Notes |
|---|---|---|
location | location table | Location to query |
Example
return {
api_version = 1,
on_enter_combat = function(context)
if context.world:is_passable_at_location(context.boss:get_location()) then
context.log:info("Boss is in a passable block")
end
end
}
world:is_passthrough_at_location(location)
Returns true if the block at the location is not solid (similar to passable, but based on solid check).
| Parameter | Type | Notes |
|---|---|---|
location | location table | Location to query |
Example
return {
api_version = 1,
on_enter_combat = function(context)
local loc = context.boss:get_location()
local above = { x = loc.x, y = loc.y + 1, z = loc.z, world = loc.world }
if context.world:is_passthrough_at_location(above) then
context.log:info("Boss can move upward")
end
end
}
world:is_on_floor_at_location(location)
Returns true if the block at the location is non-solid and the block one Y unit below is solid. This does not inspect an entity's grounded state, collision box, or headroom.
| Parameter | Type | Notes |
|---|---|---|
location | location table | Location to query |
Example
return {
api_version = 1,
on_enter_combat = function(context)
if context.world:is_on_floor_at_location(context.boss:get_location()) then
context.boss:send_message("&aThe boss slams the ground!")
context.world:spawn_particle_at_location(context.boss:get_location(), {
particle = "CLOUD",
amount = 30,
})
end
end
}
world:is_standing_on_material(location, material)
Returns true if the block directly below the location matches the given material.
| Parameter | Type | Notes |
|---|---|---|
location | location table | Location to query |
material | string | Material name to check |
Example
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then return end
if context.world:is_standing_on_material(context.player:get_location(), "SOUL_SAND") then
context.player:send_message("&8The soul sand saps your strength!")
context.player:apply_potion_effect("SLOWNESS", 60, 2)
end
end
}
world:set_world_time(time) / world:set_world_time(location, time)
Sets the entire world's time. You can optionally pass a location to select the world. The old time is not restored when the script closes.
| Parameter | Type | Notes |
|---|---|---|
location | location table (optional) | Which world to affect. Defaults to the boss world. |
time | int | World time in ticks (0 = dawn, 6000 = noon, 13000 = night, 18000 = midnight) |
Example
return {
api_version = 1,
on_enter_combat = function(context)
-- Set to midnight when combat starts
context.world:set_world_time(18000)
context.boss:send_message("&8Darkness falls...")
end
}
world:set_world_weather(weather, duration) / world:set_world_weather(location, weather, duration)
Sets weather in the boss world, or the world of the optional location. Names ignore case; unknown names only warn. RAIN/CLEAR set the weather-duration timer, while THUNDER sets the thunder-duration timer. This affects the entire world and does not restore the previous state when the script closes.
| Parameter | Type | Default | Notes |
|---|---|---|---|
location | location table (optional) | boss world | Which world to affect |
weather | string | "CLEAR", "RAIN" (or "PRECIPITATION"), "THUNDER" | |
duration | int | 6000 | Weather duration in ticks |
Example
return {
api_version = 1,
on_enter_combat = function(context)
-- Start a thunderstorm for 200 ticks
context.world:set_world_weather("THUNDER", 200)
context.boss:send_message("&eA storm gathers!")
end
}
world:generate_fake_explosion(blockLocations, sourceLocation)
Removes actual blocks and creates falling-block debris through EliteMobs' explosion-regeneration system. The debris has entity damage enabled, so this is not a harmless particle effect. doExplosionRegen must be enabled; the WorldGuard explosion-regeneration flag is checked when a source location is supplied. Air, fire, liquids and tracked temporary blocks are skipped, and dependent neighboring blocks can be included. Restoration starts after 1200 ticks and processes one recorded block per tick. Keep all input locations in the boss's world. The operation returns nil and is not undone by closing the script.
| Parameter | Type | Notes |
|---|---|---|
blockLocations | table of location tables | Array of block locations to "explode" |
sourceLocation | location table (optional) | Explosion source for directional calculations |
Example
return {
api_version = 1,
on_enter_combat = function(context)
local loc = context.boss:get_location()
local blocks = {}
for dx = -1, 1 do
for dz = -1, 1 do
table.insert(blocks, { x = loc.x + dx, y = loc.y, z = loc.z + dz, world = loc.world })
end
end
context.world:generate_fake_explosion(blocks, loc)
end
}
world:run_console_command(command)
Dispatches a console command immediately as the server and returns nil, without exposing the command's success result. No player or boss placeholders are substituted automatically.
Vanilla commands report their result, and with the default broadcast-console-to-ops=true the server shows every result to all online operators. Commands run often, such as data merge entity, kill or damage, flood their chat. Use the native methods instead: spawn_display_at_location for visuals, the armor stand methods for stands, entity:remove() to remove what a script spawned, and entity:deal_custom_damage(amount) for exact damage from the boss.
| Parameter | Type | Notes |
|---|---|---|
command | string | The command to run (without the leading /) |
Example
return {
api_version = 1,
on_enter_combat = function(context)
context.world:run_console_command("say The boss has entered phase 2!")
end
}
world:run_empowered_lightning_task_at_location(location)
Starts an independent Bukkit task at the captured location. It emits SOUL_FIRE_FLAME for 60 iterations, then strikes real lightning with the spawn bypass and launches a downward fireball with explosion yield 5, tagged as ender_dragon_empowered_lightning.yml. This is a damaging attack, not just a visual. Closing or pausing the Lua script does not cancel this task, and the helper does not consult the warning-effects setting.
| Parameter | Type | Notes |
|---|---|---|
location | location table | Where to strike |
Example
return {
api_version = 1,
on_enter_combat = function(context)
context.world:run_empowered_lightning_task_at_location(context.boss:get_location())
context.boss:send_message("&eFeel the power of the storm!")
end
}
world:spawn_fake_gold_nugget_at_location(location, velocity, hasGravity)
Creates a packet-based gold nugget visual for nearby players; players and hoppers cannot pick it up. Creation alone does not advance it or deal damage. Pass it to run_fake_gold_nugget_damage once as part of a batch, or remove it explicitly.
| Parameter | Type | Default | Notes |
|---|---|---|---|
location | location table | Spawn location | |
velocity | vector table | Initial velocity | |
hasGravity | boolean | false | Whether the projectile has gravity |
Returns a fake projectile table, or nil for an unresolved location/vector, with methods: get_location(), set_gravity(bool), remove(), is_removed().
Example
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then return end
if not context.cooldowns:check_local("nugget", 60) then return end
local dir = context.vectors.get_vector_between_locations(
context.boss:get_location(),
context.player:get_location()
)
dir = context.vectors.normalize_vector(dir)
local nugget = context.world:spawn_fake_gold_nugget_at_location(
context.boss:get_location(),
{ x = dir.x, y = dir.y, z = dir.z }
)
if nugget ~= nil then
context.world:run_fake_gold_nugget_damage({ nugget })
end
end
}
world:run_fake_gold_nugget_damage(projectiles)
Starts an independent Bukkit task that advances and checks a batch every tick. Call this once per batch: each call starts another task. It queries living entities in a box with half-size 0.5 at the new position, skips the originating boss and blocking players, and submits custom damage of 1 through the boss damage policy. A collision can remove the projectile even when that damage is rejected. Ground contact or the 100-tick timeout also removes it. This is a sampled collision check, not a swept ray; closing or pausing the Lua instance does not cancel the task.
| Parameter | Type | Notes |
|---|---|---|
projectiles | table | Array of fake projectile tables from spawn_fake_gold_nugget_at_location |
Example
return {
api_version = 1,
on_enter_combat = function(context)
-- Keep this batch separate from later combat entries.
local projectiles = {}
local loc = context.boss:get_location()
for i = 1, 5 do
local angle = (i / 5) * math.pi * 2
local nugget = context.world:spawn_fake_gold_nugget_at_location(
loc,
{ x = math.cos(angle) * 0.5, y = 0.3, z = math.sin(angle) * 0.5 }
)
if nugget ~= nil then table.insert(projectiles, nugget) end
end
-- Start the batch once; the helper runs its own per-tick task.
context.world:run_fake_gold_nugget_damage(projectiles)
-- Remove any surviving projectiles after 3 seconds.
context.scheduler:run_after(60, function()
for _, projectile in ipairs(projectiles) do
projectile:remove()
end
end)
end
}
world:generate_player_loot(times)
Runs the normal player-loot generator times times; nonpositive values do nothing. It requires eliteLoot, no triggered anti-exploit state, loaded player data and recorded damage of at least 10% of maximum health. Instanced lockout players are skipped. Each pass can include coins and, when random loot is enabled, random/special loot and scrolls under their normal settings and party rules. It does not rerun every authored custom drop or guarantee one item per roll. Repeated calls grant additional reward attempts; there is no once-per-boss guard.
| Parameter | Type | Default | Notes |
|---|---|---|---|
times | int | 1 | Number of loot rolls |
Example
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
local pct = context.boss:get_health() / context.boss:get_maximum_health()
if pct < 0.5 and not context.boss:has_tag("bonus_loot") then
context.boss:add_tag("bonus_loot")
context.world:generate_player_loot(2)
context.boss:send_message("&6Bonus loot drops!")
end
end
}
world:drop_bonus_coins(multiplier)
Drops bonus coins for tracked players whose recorded damage exceeds 10% of the boss's maximum health. NPCs and players without loaded EliteMobs data are skipped. The amount uses the player's scaled reward level multiplied by multiplier.
| Parameter | Type | Default | Notes |
|---|---|---|---|
multiplier | number | 2.0 | Coin amount multiplier |
Example
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
local pct = context.boss:get_health() / context.boss:get_maximum_health()
if pct < 0.25 and not context.boss:has_tag("bonus_coins") then
context.boss:add_tag("bonus_coins")
context.world:drop_bonus_coins(3.0)
context.boss:send_message("&6Gold spills everywhere!")
end
end
}
context.vectors
Vector math helpers accept dot syntax as shown; colon calls also work. Inputs accept named x, y, z fields, numeric indices 1..3, or a nested direction table. Missing components become 0. Results are new tables; inputs are not modified.
| Method | Returns | Notes |
|---|---|---|
vectors.get_vector_between_locations(loc1, loc2[, options]) | vector table | Displacement loc2 - loc1, not normalized by default |
vectors.normalize_vector(vec) | vector table | Unit vector for a nonzero input; zero remains zero |
vectors.rotate_vector(vec, pitch, yaw) | vector table | Rotate around world X first, then world Y, in degrees |
All vector tables have fields x, y, z.
vectors.get_vector_between_locations(loc1, loc2, options)
| Parameter | Type | Notes |
|---|---|---|
loc1 | location table | Start point |
loc2 | location table | End point |
options | table (optional) | Post-processing options (see below) |
Vector Options
| Key | Type | Default | Notes |
|---|---|---|---|
normalize | boolean | false | Normalize the resulting vector |
multiplier | number | 1.0 | Scale the vector |
offset | vector table | nil | Add an offset vector |
Options apply in order: normalize a nonzero displacement, multiply, then add the world-axis offset. Unresolved locations or different worlds return zero before applying any options. Coincident valid locations can still receive an offset.
Example
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then return end
if not context.cooldowns:check_local("dash", 100) then return end
-- Compute a normalized direction from boss to player, scaled to speed 2
local dir = context.vectors.get_vector_between_locations(
context.boss:get_location(),
context.player:get_location(),
{ normalize = true, multiplier = 2.0 }
)
-- Launch the boss toward the player
context.boss:set_velocity_vector(dir)
end
}
vectors.normalize_vector(vec)
| Parameter | Type | Notes |
|---|---|---|
vec | vector table | Input vector |
Example
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then return end
local raw = context.vectors.get_vector_between_locations(
context.boss:get_location(),
context.player:get_location()
)
local unit = context.vectors.normalize_vector(raw)
context.log:info("Direction: " .. unit.x .. ", " .. unit.y .. ", " .. unit.z)
end
}
vectors.rotate_vector(vec, pitch, yaw)
| Parameter | Type | Notes |
|---|---|---|
vec | vector table | Input vector |
pitch | number | Rotation around X axis in degrees |
yaw | number | Rotation around Y axis in degrees |
Example
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then return end
if not context.cooldowns:check_local("spread_shot", 100) then return end
local forward = context.vectors.normalize_vector(
context.vectors.get_vector_between_locations(
context.boss:get_location(),
context.player:get_location()
)
)
-- Fire three fireballs in a spread pattern
for _, yaw_offset in ipairs({ -30, 0, 30 }) do
local dir = context.vectors.rotate_vector(forward, 0, yaw_offset)
context.boss:summon_projectile(
"FIREBALL",
context.boss:get_eye_location(),
{
x = context.boss:get_location().x + dir.x * 10,
y = context.boss:get_location().y + dir.y * 10,
z = context.boss:get_location().z + dir.z * 10,
world = context.boss:get_location().world
},
1.0
)
end
end
}
You can build a spread of directions by rotating a base vector in a loop:
local base = context.vectors.normalize_vector(
context.vectors.get_vector_between_locations(boss_loc, target_loc)
)
for angle = 0, 330, 30 do
local dir = context.vectors.rotate_vector(base, 0, angle)
-- Use dir for spawning projectiles in a ring
end
context.settings
Read-only access to power-related configuration values. This getter reads the current setting when called; it does not automatically suppress particles or other effects. Your script must use the result to decide whether to emit a warning.
| Method | Returns | Notes |
|---|---|---|
settings:warning_visual_effects_enabled() | boolean | Whether warning visual effects are enabled in the mob combat settings |
Example
return {
api_version = 1,
on_enter_combat = function(context)
if context.settings:warning_visual_effects_enabled() then
context.world:spawn_particle_at_location(context.boss:get_location(), {
particle = "DUST",
amount = 20,
red = 255, green = 0, blue = 0,
})
end
end
}
context.event
Event data for the current hook. The available fields depend on the underlying event, so guard optional fields. Numeric damage setters require dot syntax (event.set_damage_amount(value)); transfer_damage accepts either dot or colon syntax. This boss event table does not expose the shared table's cancel, uncancel or is_cancelled members.
| Field or Method | When Available | Notes |
|---|---|---|
event.damage_amount | Damage hooks | Damage snapshot when the lazy event table is first resolved |
event.get_damage_amount() | Damage hooks | Reads the current amount after any changes |
event.damage_cause | Damage hooks | Bukkit DamageCause enum name (e.g. "ENTITY_ATTACK") |
event.cancel_event() | Cancellable events | Cancels the event |
event.set_damage_amount(value) | EliteDamageEvent-based hooks | Sets the damage directly |
event.multiply_damage_amount(multiplier) | EliteDamageEvent-based hooks | Multiplies current damage |
event.damager | Damage events with a damager entity | Raw Bukkit damager wrapper: the projectile itself for projectile hits, not its shooter |
event.projectile | Damage events where damager is a projectile | Projectile entity reference wrapper |
event.spawn_reason | on_spawn | Spawn reason enum name |
event.entity | Player-to-boss damage, on_death, zone events | The damaged boss, dead entity, or entity crossing the zone; for reinforcement damage this is the reinforcement |
event.is_damage_transfer | Player-to-boss damage, including reinforcement forwarding | Whether this hit was created by a transfer |
event:transfer_damage(target, amount) | Player-to-boss damage, including reinforcement forwarding | Submits a separate hit to another eligible elite; see the damage transfer contract |
Example: Halving incoming damage
Example
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.event ~= nil then
local dmg = context.event.damage_amount
context.log:info("Boss took " .. dmg .. " damage, halving it")
context.event.multiply_damage_amount(0.5)
end
end
}
Example: Cancelling damage from projectiles
Example
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.event ~= nil and context.event.damage_cause == "PROJECTILE" then
context.event.cancel_event()
if context.player ~= nil then
context.player:send_message("&cThe boss deflects your projectile!")
end
end
end
}
Example: Reading damage cause
Example
return {
api_version = 1,
on_player_damaged_by_boss = function(context)
if context.event ~= nil then
context.log:info("Damage cause: " .. (context.event.damage_cause or "unknown"))
end
end
}
damage_amountis a snapshot. It does not auto-refresh after callingset_damage_amount()ormultiply_damage_amount().- The fresh context supplied to a scheduled callback has
event = nil. Capturing the old hook context does not make its completed event a valid target for later modification; modify events during their hook. - Damage setters change the pending event, not health already lost. Later listeners can still modify or cancel the hit. A successful transfer submits an additional hit; it does not reduce or cancel the original automatically.
