MagmaCore Lua Scripting Engine
MagmaCore provides the shared Lua sandbox and generic ScriptInstance runtime used by multiple Nightbreak plugins. EliteMobs boss powers, EliteMobs NPC scripts, FreeMinecraftModels props, and FreeMinecraftModels items all run on this runtime and share the same file validation, sandbox, context lifecycle, scheduler ownership, cooldown contract, logging contract, zone-watch engine, and context.state model.
FreeMinecraftModels props/items and EliteMobs NPC scripts inherit the generic MagmaCore context tables directly (context.world, context.zones, context.scheduler, context.cooldowns, context.log, context.event, context.player, and context.state) and add their owner table (context.prop, context.item, or context.npc). EliteMobs boss powers run on the same runtime but expose richer boss-specific variants for several tables (context.world, context.zones, context.cooldowns, context.scheduler, context.log, context.event) plus boss-only helpers such as context.players, context.entities, context.vectors, context.settings, and context.script.
Currently, the following plugins use this engine:
- EliteMobs -- Lua powers for custom bosses (hooks like
on_boss_damaged_by_player,on_enter_combat, etc.) and NPC Lua scripts for interaction/proximity behavior (hooks likeon_npc_interact,on_npc_proximity_enter, etc.) - FreeMinecraftModels -- Lua scripts for props and custom items (hooks like
on_right_click,on_left_click,on_equip, etc.)
This page documents the generic MagmaCore engine features used by FreeMinecraftModels scripts, plus compatibility notes for EliteMobs where the same concepts have boss-specific or NPC-specific variants. For the exact EliteMobs boss context, use the EliteMobs Lua pages; for the exact NPC context, see NPC Lua Scripting.
Tiny Lua Primer
If you are completely new to Lua, here is the minimum syntax you need to write scripts for any Nightbreak plugin.
Variables
Use local to store a value:
local cooldown_key = "fire_burst"
local damage_multiplier = 1.5
local means the variable belongs only to this file or block.
Functions
Functions are reusable blocks of logic:
local function warn_player(player)
player:send_message("&cMove!")
end
Later, you can call it:
warn_player(some_player)
if checks
Use if when something should only happen sometimes:
if context.player == nil then
return
end
That means "if there is no player for this hook, stop here".
nil
nil means "no value". It is Lua's version of "nothing is here".
You will often check for nil with:
if context.event ~= nil then
-- do something with the event
end
~= means "is not equal to".
Tables
Lua uses tables for several jobs:
- Lists
- Objects with named keys
- The final returned script definition
Example of a table with named keys:
local particle = {
particle = "FLAME",
amount = 1,
speed = 0.05
}
Returning the script definition
At the end of every script file, you return one table:
return {
api_version = 1,
on_spawn = function(context)
end
}
That returned table is the script file.
Comments
Use -- to write a note for humans:
-- This cooldown stops the attack from firing every hit
context.cooldowns:set_local(60, "fire_burst")
Lua Sandbox
All Lua scripts run inside a sandboxed LuaJ environment. Several globals that could access the filesystem or the Java runtime are removed. The sandbox rules are identical across all plugins using MagmaCore.
Removed Globals
The following standard Lua globals are set to nil and cannot be used:
| Removed | Why |
|---|---|
debug | Exposes internal VM state |
dofile | Filesystem access |
io | Filesystem access |
load | Arbitrary code loading |
loadfile | Filesystem access |
luajava | Direct Java class access |
module | Module system (not needed) |
os | Operating system access |
package | Module system (not needed) |
require | Module system / filesystem access |
Available Standard Library
Everything else from the Lua standard library works normally:
| Category | Functions |
|---|---|
| Math | math.abs, math.ceil, math.floor, math.max, math.min, math.random, math.sin, math.cos, math.sqrt, math.pi, and all other math.* functions |
| String | string.byte, string.char, string.find, string.format, string.gsub, string.len, string.lower, string.match, string.rep, string.sub, string.upper, and all other string.* functions |
| Table | table.insert, table.remove, table.sort, table.concat, and all other table.* functions |
| Iterators | pairs, ipairs, next |
| Type | type, tostring, tonumber, select, unpack |
| Error handling | pcall, xpcall, error, assert |
| Other | print, rawget, rawset, rawequal, rawlen, setmetatable, getmetatable |
os library is not availableThe os library is completely removed from the sandbox. Use context.world:get_time() for world time on the generic MagmaCore surfaces; EliteMobs boss powers receive it through their boss world table's shared base. For elapsed-time logic, store tick counters in context.state via the on_game_tick hook.
print writes to the server console, but prefer context.log:info(msg). Every surface exposes context.log: FreeMinecraftModels scripts and EliteMobs NPC scripts expose info, warn, and error; EliteMobs boss powers expose info, warn, and debug.
File Contract
Every Lua script must return a table. That table is intentionally strict.
Required and Optional Top-Level Fields
| Field | Required | Type | Notes |
|---|---|---|---|
api_version | Yes | Number | Currently must be 1 |
priority | No | Number | Optional ordering value. EliteMobs boss and NPC scripts run lower values first. FMM validates the field, but prop scripts run in scripts: list order and item scripts bind only the first valid script |
| supported hook keys | No | Function | Must use one of the exact hook names supported by the plugin |
Validation Rules
- The file must return a table.
api_versionis required and must currently be1.prioritymust be numeric if present.- Every extra top-level key must be a supported hook name.
- Every hook key must point to a function.
- Unknown top-level keys are rejected.
The Lua validator accepts priority for all MagmaCore-backed scripts. EliteMobs uses it for boss and NPC script order. FreeMinecraftModels currently does not sort by priority: props follow the config scripts: list order, and custom items only bind the first valid script in that list.
Helper functions and local constants should live above the final return, not inside the returned table unless they are actual hooks.
local ANIMATION_NAME = "idle"
local function do_something(context)
context.log:info("Doing something!")
end
return {
api_version = 1,
priority = 0,
on_spawn = function(context)
do_something(context)
end
}
Common Hook Names
These hook names appear across multiple Lua runtimes. Each script type enables only the subset that makes sense for that owner: FMM prop scripts use all of these, FMM item scripts use on_game_tick from this group plus their item-specific hooks, EliteMobs boss powers use their boss hook list, and EliteMobs NPC scripts use on_spawn, on_remove, on_game_tick, on_npc_interact, on_npc_proximity_enter, on_npc_proximity_leave, on_zone_enter, and on_zone_leave.
| Hook | When it fires |
|---|---|
on_spawn | Called once when the script instance is created for supported owners (FMM props, EliteMobs bosses, and EliteMobs NPC scripts) |
on_game_tick | Called every server tick while the entity is alive/active |
on_zone_enter | Called when a runtime-supported zone reports an enter event. FMM and EliteMobs NPC zone watches are player-based; EliteMobs top-level boss zone hooks come from EliteScript zone events. |
on_zone_leave | Called when a runtime-supported zone reports a leave event. FMM and EliteMobs NPC zone watches are player-based; EliteMobs top-level boss zone hooks come from EliteScript zone events. |
Method Syntax: : vs .
In Lua, object:method(arg) is shorthand for object.method(object, arg). The MagmaCore API accepts both forms, so either works:
context.log:info("hello")
context.log.info("hello") -- same thing
All documentation uses : consistently.
Execution Budget
Every hook invocation and every callback invocation is timed. If a single call takes longer than 50 milliseconds, the script is disabled with a console warning:
[Lua] my_script.lua took 73ms in 'on_game_tick' (limit: 50ms) -- script disabled to prevent lag.
To stay within budget:
- Avoid unbounded loops inside hooks.
- Keep
on_game_tickhandlers lightweight -- they run every single tick. - Use
context.scheduler:run_repeating(...)to spread work across ticks. - Move expensive work behind
context.cooldownsor a reasonable interval.
If a Lua script throws a runtime error in any hook or scheduled callback, the script instance is immediately and permanently disabled for that entity. Fix the error in your script file -- the script will re-initialize on the next entity spawn.
context.state
A plain Lua table that persists for the script instance's entire lifetime. Use it to store flags, counters, task IDs, toggle states, and any data you need to share between hooks.
on_spawn = function(context)
context.state.is_open = false
context.state.click_count = 0
context.state.task_id = nil
end,
on_right_click = function(context)
context.state.click_count = (context.state.click_count or 0) + 1
end
Only context.state is meant for script-owned persistent data. Most other context tables (context.world, context.event, etc.) are rebuilt or lazily recreated as needed. FreeMinecraftModels caches context.prop per prop for performance, but its live fields such as current_location are lazy, so reads still reflect current state. context.state survives from the moment the script instance is created until it is destroyed.
context.log
Console logging methods, available on every script surface. Messages are prefixed with the script filename in the server console. FreeMinecraftModels scripts and EliteMobs NPC scripts expose info, warn, and error; EliteMobs boss powers expose info, warn, and debug.
| Method | Notes |
|---|---|
log:info(message) | Informational message |
log:warn(message) | Warning message |
log:error(message) | Warning-level message (logged at WARN level, same as log:warn) |
The EliteMobs context.log registers debug instead of error. Use log:debug(message) for debug output (appears as info-level with a debug prefix).
context.log:info("Script loaded!")
context.log:warn("Something unexpected happened")
-- Generic/FMM/NPC logger:
context.log:error("Critical failure in zone setup")
-- EliteMobs boss logger:
context.log:debug("Boss debug detail")
context.cooldowns
The cooldowns table manages time-based cooldowns for your scripts. There are two scopes:
- Local cooldowns are per-script-instance and identified by a string key. If no key is provided, the script's filename is used as the default key.
- Global cooldowns are shared across all scripts on the same owner entity where the runtime exposes global cooldown methods (e.g. all scripts on the same prop, or all Lua powers on the same boss).
cooldowns:check_local(key?, duration)
The most common method. Checks if the cooldown is ready, and if so, starts it and returns true. If not ready, returns false. This is an atomic check-and-set — no race conditions.
| Parameter | Type | Notes |
|---|---|---|
key | string (optional) | Cooldown identifier. Defaults to the script filename. |
duration | int | Cooldown duration in ticks (20 = 1 second) |
on_right_click = function(context)
-- Only allow this action once every 3 seconds
if not context.cooldowns:check_local("interact", 60) then
return
end
-- ... do the action
end
All script surfaces — boss powers, NPC scripts, and FreeMinecraftModels scripts — use the shared order shown above: check_local(key?, duration). (Earlier experimental NPC builds used check_local(duration, key?); update any old NPC scripts to the shared order.)
cooldowns:local_ready(key?)
Returns true if the local cooldown has expired (or was never set), false if still active.
cooldowns:local_remaining(key?)
Returns the number of ticks remaining on the local cooldown, or 0 if ready.
cooldowns:set_local(duration, key?)
Sets a local cooldown without checking if one is already active. Use this for unconditional cooldown resets.
| Parameter | Type | Notes |
|---|---|---|
duration | int | Duration in ticks. Pass 0 or negative to clear. |
key | string (optional) | Cooldown identifier. Defaults to the script filename. |
cooldowns:global_ready()
Returns true if the global cooldown (shared across all scripts on the same entity) has expired.
cooldowns:set_global(duration)
Sets the global cooldown.
| Parameter | Type | Notes |
|---|---|---|
duration | int | Duration in ticks |
The "owner" of a global cooldown depends on the script type:
- EliteMobs Lua powers — shared per
EliteEntity(one bucket per boss).global_ready()/set_global()use the boss's built-in power cooldown system; local cooldowns are also shared across all powers on the same boss. - EliteMobs NPC scripts — global cooldowns are shared per NPC (one bucket across all scripts on that NPC); local cooldowns are per NPC script instance.
- FMM prop scripts — shared per
PropEntity(one bucket per placed prop). - FMM item scripts — shared per player UUID (one bucket per player). Local cooldowns on items are persisted across equip/unequip cycles, keyed by
playerUUID → "itemId:scriptFile" → key, so a cooldown survives the player swapping the item out of their hotbar and back.
context.scheduler
The scheduler lets you run delayed and repeating tasks. All tasks are owned by the script instance and cancelled automatically when the script instance is destroyed (e.g. when a prop is removed or a boss dies).
This section uses the MagmaCore scheduler names (run_later, run_repeating, cancel). The EliteMobs names (run_after(ticks, callback), run_every(interval, callback), cancel_task(taskId)) are accepted as aliases on every surface, so either style works in any script.
scheduler:run_later(ticks, callback)
Runs a callback once after a delay. Returns a numeric task ID.
| Parameter | Type | Notes |
|---|---|---|
ticks | int | Delay in server ticks (20 ticks = 1 second) |
callback | function | Called with a fresh context when the delay expires |
context.scheduler:run_later(40, function(delayed_context)
delayed_context.log:info("2 seconds have passed!")
end)
scheduler:run_repeating(delay, interval, callback)
Runs a callback repeatedly at a fixed interval. Returns a numeric task ID.
| Parameter | Type | Notes |
|---|---|---|
delay | int | Initial delay in ticks before the first run |
interval | int | Ticks between each subsequent run |
callback | function | Called with a fresh context each interval |
context.state.particle_task = context.scheduler:run_repeating(0, 20, function(tick_context)
tick_context.log:info("Another second passed!")
end)
scheduler:cancel(taskId)
Cancels a scheduled task by its ID.
| Parameter | Type | Notes |
|---|---|---|
taskId | int | The task ID returned by run_later or run_repeating |
If you start a repeating task, always cancel it in your cleanup hook (on_destroy for props, on_unequip for items). EliteMobs boss powers should use their own scheduler names and usually cancel repeating work in on_exit_combat / on_death. Forgetting to cancel leaves a background task running until the script instance is destroyed, which wastes performance and can cause errors.
Scheduled callbacks receive a fresh context as their parameter. Always use the callback's own context argument, not the outer context from the hook that created the callback. The outer context may contain stale references.
context.world
The world table provides methods for querying and interacting with the Minecraft world. It is built from the entity's current world.
The methods documented here are the shared MagmaCore context.world table. FreeMinecraftModels props/items and EliteMobs NPC scripts receive this table directly, including strike_lightning(...). EliteMobs boss powers start from this same base table and then add or override EliteMobs-specific methods such as protected lightning, fake projectiles, reinforcement spawning, and player-loot helpers; see EliteMobs World & Environment.
context.world accepts both coordinate-style calls (e.g. strike_lightning(x, y, z), spawn_particle(name, x, y, z, ...)) and location-table-style overloads (e.g. strike_lightning_at_location(loc), spawn_particle_at_location(loc, ...)) on the generic surfaces. EliteMobs boss powers also support both broad styles, but some location-table methods have boss-specific behavior.
world.name
A string field containing the world name.
world:get_block_at(x, y, z)
Returns the material name of the block at the given coordinates as a lowercase string (e.g. "stone", "air"). If the target chunk is not loaded, this returns "air" instead of force-loading the chunk.
| Parameter | Type | Notes |
|---|---|---|
x | int | Block X coordinate |
y | int | Block Y coordinate |
z | int | Block Z coordinate |
world:set_block_at(x, y, z, material)
Sets the block at the given coordinates. Runs on the main thread and avoids force-loading chunks.
| Parameter | Type | Notes |
|---|---|---|
x | int | Block X coordinate |
y | int | Block Y coordinate |
z | int | Block Z coordinate |
material | string | Bukkit Material name, lowercase (e.g. "stone", "air", "oak_planks") |
Returns true when the material name is valid and the set was queued, false when the material is invalid. If the target chunk is unloaded by the time the queued task runs, the block change is skipped.
world:spawn_particle(particle, x, y, z, count, dx, dy, dz, speed)
Spawns particles at a location.
| Parameter | Type | Default | Notes |
|---|---|---|---|
particle | string | required | Bukkit Particle enum name, UPPER_CASE (e.g. "FLAME", "HAPPY_VILLAGER") |
x | number | required | X coordinate |
y | number | required | Y coordinate |
z | number | required | Z coordinate |
count | int | 1 | Number of particles |
dx | number | 0 | X spread/offset |
dy | number | 0 | Y spread/offset |
dz | number | 0 | Z spread/offset |
speed | number | 0 | Particle speed |
Some particle types require extra data that is not supported by this simple API. DUST, BLOCK_CRACK, FALLING_DUST, BLOCK_DUST, and ITEM_CRACK may fail or produce no visible effect. Use data-free alternatives instead: CLOUD, SMOKE, CAMPFIRE_COSY_SMOKE, SNOWFLAKE, FLAME, HAPPY_VILLAGER, HEART, NOTE, ENCHANT, etc.
Because the simple API does not provide Bukkit particle data objects, data-required particles may throw an underlying Bukkit exception and disable the script instance. Prefer the data-free particle names above for FMM/MagmaCore scripts.
world:play_sound(sound, x, y, z, volume, pitch)
Plays a sound at a location.
| Parameter | Type | Default | Notes |
|---|---|---|---|
sound | string | required | Bukkit Sound enum name, UPPER_CASE (e.g. "ENTITY_EXPERIENCE_ORB_PICKUP") |
x | number | required | X coordinate |
y | number | required | Y coordinate |
z | number | required | Z coordinate |
volume | number | 1.0 | Volume |
pitch | number | 1.0 | Pitch |
world:strike_lightning(x, y, z)
Strikes lightning at a location (visual and damaging).
| Parameter | Type | Notes |
|---|---|---|
x | number | X coordinate |
y | number | Y coordinate |
z | number | Z coordinate |
world:get_time()
Returns the current world time in ticks.
world:set_time(ticks)
Sets the world time.
| Parameter | Type | Notes |
|---|---|---|
ticks | int | World time (0 = dawn, 6000 = noon, 13000 = night, 18000 = midnight) |
world:get_nearby_entities(x, y, z, radius)
Returns an array of entity wrapper tables for all entities within a bounding box centered at the given coordinates.
| Parameter | Type | Notes |
|---|---|---|
x | number | Center X |
y | number | Center Y |
z | number | Center Z |
radius | number | Search radius (used as half-extent in all three axes) |
This returns ALL entities in range, including non-living entities (armor stands, dropped items, etc.). Always guard with if entity.damage then before calling living-entity methods.
world:get_nearby_players(x, y, z, radius)
Returns an array of player wrapper tables for all players within a bounding box centered at the given coordinates.
| Parameter | Type | Notes |
|---|---|---|
x | number | Center X |
y | number | Center Y |
z | number | Center Z |
radius | number | Search radius |
world:spawn_entity(entity_type, x, y, z)
Spawns a vanilla Minecraft entity at the given location.
| Parameter | Type | Notes |
|---|---|---|
entity_type | string | Bukkit entity type name, lowercase (e.g. "zombie", "skeleton", "pig") |
x | number | X coordinate |
y | number | Y coordinate |
z | number | Z coordinate |
Returns an entity table for the spawned entity (with living entity methods if applicable), or nil if the entity type is invalid.
world:get_highest_block_y(x, z)
Returns the Y coordinate of the highest non-air block at the given X/Z position.
| Parameter | Type | Notes |
|---|---|---|
x | int | Block X coordinate |
z | int | Block Z coordinate |
world:raycast(from_x, from_y, from_z, dir_x, dir_y, dir_z, [max_distance])
Casts a ray from a point in a direction and returns information about what it hits.
| Parameter | Type | Default | Notes |
|---|---|---|---|
from_x | number | required | Origin X |
from_y | number | required | Origin Y |
from_z | number | required | Origin Z |
dir_x | number | required | Direction X component |
dir_y | number | required | Direction Y component |
dir_z | number | required | Direction Z component |
max_distance | number | 50 | Maximum ray distance |
Returns a table with the following fields:
| Field | Type | Notes |
|---|---|---|
hit_entity | entity table or nil | The first entity hit by the ray, or nil if none |
hit_location | location table or nil | The exact point where the ray hit something |
hit_block | table or nil | {x, y, z, material} of the block hit, or nil if no block was hit |
world:spawn_firework(x, y, z, colors, [type], [power])
Spawns a firework rocket at the given location.
| Parameter | Type | Default | Notes |
|---|---|---|---|
x | number | required | X coordinate |
y | number | required | Y coordinate |
z | number | required | Z coordinate |
colors | table | required | Array of color strings, e.g. {"RED", "BLUE", "WHITE"} |
type | string | "BALL" | Firework shape: "BALL", "BALL_LARGE", "STAR", "BURST", "CREEPER" |
power | int | 1 | Flight power, 0-127 |
-- Example: red and gold firework burst
context.world:spawn_firework(loc.x, loc.y, loc.z, {"RED", "GOLD"}, "BURST", 2)
world:place_temporary_block(x, y, z, material, [ticks], [require_air])
Places a block that automatically reverts to its original state after a delay.
| Parameter | Type | Default | Notes |
|---|---|---|---|
x | int | required | Block X coordinate |
y | int | required | Block Y coordinate |
z | int | required | Block Z coordinate |
material | string | required | Bukkit Material name (e.g. "stone", "ice") |
ticks | int | 0 | Duration in ticks before the block reverts. 0 means permanent. |
require_air | boolean | false | If true, only places the block if the target is air |
Returns false if the material is invalid or the target chunk is unloaded. Otherwise returns true once the placement request is accepted. If require_air is true and the target block is not air, the manager skips the replacement even though the request itself was accepted.
world:drop_item(x, y, z, material, [amount])
Drops an item entity at the given location with natural scatter.
| Parameter | Type | Default | Notes |
|---|---|---|---|
x | number | required | X coordinate |
y | number | required | Y coordinate |
z | number | required | Z coordinate |
material | string | required | Bukkit Material name |
amount | int | 1 | Stack size |
Returns an entity table for the dropped item, or nil if the material was invalid.
context.zones
The zones table lets scripts create spatial zones and watch them for player enter/leave events. FMM props, FMM items, and EliteMobs NPC scripts use the generic create_sphere(...) / create_cylinder(...) / create_cuboid(...) plus watch(...) / unwatch(...) methods documented here, firing the on_zone_enter / on_zone_leave hooks. Zones are tied to the script instance and cleaned up automatically when the script instance is destroyed. EliteMobs boss powers use a separate boss-specific zone table with methods such as get_entities_in_zone(...), get_locations_in_zone(...), and watch_zone(...); see EliteMobs Zones & Targeting.
zones:create_sphere(x, y, z, radius)
Creates a sphere zone centered at the given coordinates. Returns a numeric zone handle.
| Parameter | Type | Notes |
|---|---|---|
x | number | Center X |
y | number | Center Y |
z | number | Center Z |
radius | number | Sphere radius |
zones:create_cylinder(x, y, z, radius, height)
Creates a cylinder zone. Returns a numeric zone handle.
| Parameter | Type | Notes |
|---|---|---|
x | number | Center X |
y | number | Center Y (base) |
z | number | Center Z |
radius | number | Cylinder radius |
height | number | Cylinder height |
zones:create_cuboid(x, y, z, xSize, ySize, zSize)
Creates a cuboid zone. Returns a numeric zone handle.
| Parameter | Type | Notes |
|---|---|---|
x | number | Center X |
y | number | Center Y |
z | number | Center Z |
xSize | number | Half-extent in X |
ySize | number | Half-extent in Y |
zSize | number | Half-extent in Z |
zones:watch(handle, onEnterCallback, onLeaveCallback)
Starts watching a zone for player enter/leave events. The callback parameters must be Lua functions to enable the corresponding hook, but the function bodies are not invoked directly. Instead, enter/leave logic fires through the script's on_zone_enter / on_zone_leave hooks.
| Parameter | Type | Notes |
|---|---|---|
handle | int | Zone handle from a create_* call |
onEnterCallback | function or nil | A function enables the on_zone_enter hook for this zone; the function body is ignored |
onLeaveCallback | function or nil | A function enables the on_zone_leave hook for this zone; the function body is ignored |
Returns true if the watch was set up successfully, nil if the zone handle was invalid.
Zone watches are checked every server tick against all players in the same world. Keep zone counts reasonable to avoid performance overhead.
zones:unwatch(handle)
Stops watching a zone and cleans up its resources.
| Parameter | Type | Notes |
|---|---|---|
handle | int | Zone handle to stop watching |
Example: Proximity trigger zone
return {
api_version = 1,
on_spawn = function(context)
local loc = context.prop.current_location
if loc == nil then return end
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, 5)
-- Pass placeholder functions to enable on_zone_enter and on_zone_leave hooks.
-- The placeholder bodies are ignored; the actual logic goes in the hooks below.
context.zones:watch(handle, function() end, function() end)
context.state.zone_handle = handle
end,
on_zone_enter = function(context)
context.log:info("Player entered zone!")
end,
on_zone_leave = function(context)
context.log:info("Player left zone!")
end,
on_destroy = function(context)
if context.state.zone_handle then
context.zones:unwatch(context.state.zone_handle)
end
end
}
context.event
The event table is present when the current hook was triggered by a Bukkit event (for example on_right_click) or when the runtime supplies a player actor for a generic MagmaCore zone hook. It is not present during on_game_tick, scheduled callbacks, or lifecycle hooks that have no triggering player.
For generic on_zone_enter / on_zone_leave hooks, context.player and context.event.player identify the player entering or leaving the zone. Those zone hooks are not backed by a cancellable Bukkit event, so cancel() and uncancel() are not present there.
| Field / Method | Type | Notes |
|---|---|---|
is_cancelled | boolean | Cancellation state when the context was built. This field is not refreshed after calling cancel() or uncancel(). |
cancel() | method | Cancels the event (prevents default behavior) |
uncancel() | method | Un-cancels a previously cancelled event |
player | entity table | The player involved in the event, if any |
on_right_click = function(context)
if context.event then
context.event:cancel() -- prevent the default right-click interaction
end
end
EliteMobs power scripts have a more detailed event table with damage amounts, damage causes, damager references, and damage modification methods. See Lua API Reference for the full EliteMobs event fields.
em Helper Namespace
The em table is available at file load time (before any hook runs). It provides helper constructors for building location tables, vector tables, and zone definitions used throughout the API.
| Function | Purpose |
|---|---|
em.create_location(x, y, z [, world, yaw, pitch]) | Create a location table with optional world name, yaw, and pitch. The returned table also has an .add(dx, dy, dz) method that offsets the location in-place and returns itself for chaining. |
em.create_vector(x, y, z) | Create a vector table |
em.zone.create_sphere_zone(radius) | Create a sphere zone definition |
em.zone.create_dome_zone(radius) | Create a dome zone definition |
em.zone.create_cylinder_zone(radius, height) | Create a cylinder zone definition |
em.zone.create_cuboid_zone(x, y, z) | Create a cuboid zone definition |
em.zone.create_cone_zone(length, radius) | Create a cone zone definition |
em.zone.create_static_ray_zone(length, thickness) | Create a static ray zone definition |
em.zone.create_rotating_ray_zone(length, point_radius, animation_duration) | Create a rotating ray zone definition |
em.zone.create_translating_ray_zone(length, point_radius, animation_duration) | Create a translating ray zone definition |
em.location.is_in_dungeon(loc) | Check if a location is inside any registered dungeon region |
em.location.is_protected(loc) | Check if a location is inside any protected region (WorldGuard, GriefPrevention, EM regions, etc.) |
em.location.owned_by(loc, namespace) | Check whether a specific plugin namespace owns the location (e.g. "EliteMobs") |
em.location.owners(loc) | Array of namespace strings that own the location |
em.location.kinds_at(loc) | Array of kind tags at the location ("elitemobs", "world_package", "open_world_dungeon", dungeon size category) |
em.location.has_kind(loc, kind) | Check if the location is tagged with the given kind |
Zone builders return chainable tables with :set_center(loc) (or :set_origin(loc) / :set_destination(loc) depending on zone type):
-- At file scope: create a reusable zone shape
local blast_zone = em.zone.create_sphere_zone(5)
return {
api_version = 1,
on_spawn = function(context)
-- Anchor the zone at call time
blast_zone:set_center(context.boss:get_location())
local entities = context.zones:get_entities_in_zone(blast_zone)
-- ...
end
}
The em namespace is particularly useful in EliteMobs where zone definitions are used with context.zones:get_entities_in_zone() and context.script. In FreeMinecraftModels, the context.zones:create_sphere(...) / create_cylinder(...) / create_cuboid(...) methods are more commonly used for simple watch-based zones.
Entity Tables
Entity tables are returned from world queries and event data. They provide a layered set of fields and methods depending on the entity type.
Entity Base Fields
| Field | Type | Notes |
|---|---|---|
uuid | string | The entity's UUID |
entity_type | string | The entity type (e.g. "player", "zombie", "skeleton", "villager") |
is_valid | boolean | Whether the entity reference is still valid |
is_dead | boolean | Whether the entity is dead |
is_player | boolean | Whether the entity is a player |
is_hostile | boolean | Whether the entity is a hostile mob (zombie, skeleton, etc.) |
is_passive | boolean | Whether the entity is a passive mob (cow, pig, chicken, etc.) |
current_location | location table | The entity's current position (x, y, z, world, yaw, pitch) |
world | string | The world name the entity is in |
Entity Base Methods
| Method | Returns | Notes |
|---|---|---|
teleport(location_table) | void | Teleports the entity. The location table must have x, y, z, world fields; yaw and pitch are optional. |
remove() | void | Removes the entity from the world. Only acts if the entity is still valid. |
set_silent(flag) | void | Suppresses or re-enables the entity's sounds. |
set_invulnerable(flag) | void | Makes the entity invulnerable or vulnerable to damage. |
set_gravity(flag) | void | Enables or disables gravity for the entity. |
set_glowing(flag) | void | Toggles the glowing outline effect on the entity. |
Living Entity Fields
Living entities (players, mobs, etc.) have all entity base fields plus:
| Field | Type | Notes |
|---|---|---|
health | number | Current health |
maximum_health | number | Maximum health |
name | string | Display name |
is_alive | boolean | Whether the entity is alive |
Living Entity Methods
| Method | Returns | Notes |
|---|---|---|
damage(amount) | void | Deals the given amount of damage to the entity |
push(x, y, z) | void | Applies a velocity impulse |
set_facing(x, y, z) | void | Sets the direction the entity faces |
add_potion_effect(effect, duration, amplifier) | void | Adds a potion effect. effect is a string (e.g. "speed", "slowness", "regeneration"). duration is in ticks. amplifier is the effect level minus 1 (0 = level I). |
remove_potion_effect(effect) | void | Removes a potion effect by name |
get_scale() | number | Returns the current entity scale (defaults to 1.0 on servers without the generic.scale attribute) |
set_scale(value) | void | Sets the entity scale via the generic.scale attribute. No-op on servers before 1.20.5. |
Not all entities returned by get_nearby_entities() are living entities. You can use entity.is_player, entity.is_hostile, or entity.is_passive to filter by category, or check if entity.damage then before calling living-entity methods like damage(), push(), or add_potion_effect().
Plugin Integration Fields
Entity tables automatically include fields for detecting and interacting with entities managed by other Nightbreak plugins. These fields are only populated when the relevant plugin is installed -- otherwise they default to false / nil with zero overhead.
EliteMobs Fields
Available when EliteMobs is installed.
| Field | Type | Notes |
|---|---|---|
is_elite | boolean | Whether the entity is an EliteMobs elite |
is_custom_boss | boolean | Whether the entity is an EliteMobs custom boss (also available inside the elite subtable) |
is_significant_boss | boolean | Whether the entity is a custom boss with a health multiplier over 1 (i.e. a designed encounter, not a filler elite) |
elite | table or nil | Elite info subtable (see below). nil if the entity is not an elite. |
The elite subtable contains:
| Field | Type | Notes |
|---|---|---|
elite.level | int | The elite's level |
elite.name | string | The elite's display name |
elite.health | number | Current health |
elite.max_health | number | Maximum health |
elite.is_custom_boss | boolean | Whether this is a custom boss (as opposed to a natural elite) |
elite.health_multiplier | number | Config-defined health multiplier |
elite.damage_multiplier | number | Config-defined damage multiplier |
elite:remove() | void | Removes the elite through EliteMobs' proper removal pipeline (cleans up tracking, loot, etc.) |
Example: Damage elites differently
local entities = context.world:get_nearby_entities(x, y, z, 5)
for _, entity in ipairs(entities) do
if entity.is_elite then
-- Deal double damage to elites
entity:damage(20)
context.log:info("Hit elite: " .. entity.elite.name .. " (level " .. entity.elite.level .. ")")
elseif entity.is_hostile then
entity:damage(10)
end
end
FreeMinecraftModels Fields
Available when FreeMinecraftModels is installed.
| Field | Type | Notes |
|---|---|---|
is_modeled | boolean | Whether the entity has an FMM model attached (DynamicEntity or PropEntity) |
is_prop | boolean | Whether the entity is an FMM prop (static decorative entity) |
model | table or nil | Model info subtable (see below). nil if the entity is not modeled. |
The model subtable contains:
| Field | Type | Notes |
|---|---|---|
model.model_id | string | The model blueprint ID (e.g. "torch_01") |
model.is_dynamic | boolean | Whether the modeled entity is a DynamicEntity (mob/boss model) rather than a static prop |
model:play_animation(name, [blend], [loop]) | boolean | Plays a named animation. blend defaults to false, loop defaults to false. Returns true if the animation was found. |
model:stop_animations() | void | Stops all currently playing animations on the model. |
model:remove() | void | Removes the modeled entity through FMM's proper removal pipeline. |
The model bridge (available on any entity) defaults blend and loop to false. The prop table play_animation defaults both to true because props typically want blended, looped animations.
Example: Filter entities by type
local entities = context.world:get_nearby_entities(x, y, z, 10)
for _, entity in ipairs(entities) do
if entity.is_prop then
-- Skip props
elseif entity.is_player then
entity:damage(5)
elseif entity.entity_type == "villager" then
-- Don't hurt villagers
elseif entity.is_elite then
entity:damage(20)
elseif entity.is_hostile then
entity:damage(10)
end
end
Player-Specific Fields
Player entities have all entity and living entity fields plus:
| Field | Type | Notes |
|---|---|---|
game_mode | string | The player's game mode ("creative", "survival", "adventure", "spectator") |
Player-Specific Methods
| Method | Returns | Notes |
|---|---|---|
send_message(msg) | void | Sends a chat message to the player. Supports & color codes. |
get_held_item() | table or nil | Returns {type, amount, display_name} for the item in the player's main hand, or nil if empty. type is the uppercase Bukkit Material name, such as "GOLD_INGOT". |
consume_held_item(amount?) | void | Consumes items from the player's main hand. amount defaults to 1 |
has_item(material, amount?) | boolean | Returns true if the player has at least amount (default 1) of the given material anywhere in their inventory |
get_target_entity([range]) | entity table or nil | Returns the entity the player is looking at via raycast, or nil if none. Default range is 50. |
get_eye_location() | location table | Returns a location table at the player's eye height |
get_look_direction() | table | Returns a {x, y, z} direction vector for where the player is looking |
send_block_change(x, y, z, material, [ticks]) | boolean | Sends a fake block visible only to this player. If ticks is provided, the fake block auto-resets after that duration. Returns true on success, false if the material is invalid. |
reset_block(x, y, z) | boolean | Resets a fake block back to the real block for this player. Always returns true. |
sleep(x, y, z) | void | Makes the player enter a bed sleep animation at the given coordinates. The block is automatically restored when the player stops sleeping. |
wake_up() | void | Wakes up a sleeping player. |
Player UI Methods
These methods are available on any player entity table and provide ways to display information to the player through Minecraft's built-in UI elements.
player:show_boss_bar(text, [color], progress, [ticks])
Shows a boss bar to the player.
| Parameter | Type | Default | Notes |
|---|---|---|---|
text | string | required | The text to display. Supports & color codes. |
color | string | "WHITE" | Bar color. One of: "RED", "BLUE", "GREEN", "YELLOW", "PURPLE", "PINK", "WHITE" |
progress | number | required | Fill amount from 0.0 (empty) to 1.0 (full) |
ticks | int | nil | Optional auto-dismiss delay in ticks. If omitted, the bar stays until manually hidden. |
player:hide_boss_bar()
Removes the boss bar from the player's screen. Takes no parameters.
player:show_action_bar(text, [ticks])
Shows text in the action bar area (above the hotbar).
| Parameter | Type | Default | Notes |
|---|---|---|---|
text | string | required | The text to display. Supports & color codes. |
ticks | int | nil | Optional duration in ticks. If provided, the message is re-sent every 40 ticks to keep it visible for the full duration. |
player:show_title(title, [subtitle], fade_in, stay, fade_out)
Shows a title screen to the player.
| Parameter | Type | Default | Notes |
|---|---|---|---|
title | string | required | Main title text. Supports & color codes. |
subtitle | string | "" | Subtitle text below the main title. Optional. |
fade_in | int | required | Fade-in duration in ticks |
stay | int | required | How long the title stays on screen in ticks |
fade_out | int | required | Fade-out duration in ticks |
Next Steps
For plugin-specific hooks, APIs, and workflows:
- EliteMobs: Getting Started | NPC Scripts | Hooks & Lifecycle | Boss & Entities | World & Environment | Zones & Targeting
- FreeMinecraftModels: Getting Started | Prop & Item API | Examples