MagmaCore Lua Scripting Engine
MagmaCore provides a shared Lua scripting engine used by multiple Nightbreak plugins. The engine handles sandboxing, scheduling, zone management, world interaction, entity tables, and player UI -- all with a consistent API across plugins.
Currently, the following plugins use this engine:
- EliteMobs -- Lua powers for custom bosses (hooks like
on_boss_damaged_by_player,on_enter_combat, etc.) - FreeMinecraftModels -- Lua scripts for props and custom items (hooks like
on_right_click,on_left_click,on_equip, etc.)
This page documents the shared engine features. For plugin-specific hooks, APIs, and workflows, see the plugin pages linked above.
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. If you need timing information, use context.world:get_time() for world time or store timestamps in context.state using tick counters with on_tick / on_game_tick.
print writes to the server console, but prefer context.log:info(msg) for output. Log messages are prefixed with the script filename, making it easier to trace which script produced the message.
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 | Execution priority. Lower values run first. Defaults to 0 |
| 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.
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
}
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_tick/on_tickhandlers lightweight -- they run every single tick. - Use
context.scheduler:run_repeating(...)to spread work across ticks. - Move expensive work behind a state-based cooldown or a reasonable interval.
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 persists between hook calls. All other context tables (context.prop, context.world, context.event, etc.) are rebuilt fresh each time. context.state is not rebuilt -- it survives from the moment the script instance is created until it is destroyed.
context.log
Console logging methods. Messages are prefixed with the script filename in the server console.
| Method | Notes |
|---|---|
log:info(message) | Informational message |
log:warn(message) | Warning message |
log:error(message) | Error-level message |
context.log:info("Script loaded!")
context.log:warn("Something unexpected happened")
context.log:error("Critical failure in zone setup")
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).
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_exit_combat / on_death for bosses, on_unequip for items). 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.
EliteMobs extends context.world with additional methods for spawning bosses, reinforcements, falling blocks, fireworks, splash potions, and temporary blocks. See EliteMobs World & Environment for the full extended API. The methods documented here are available across all plugins.
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").
| 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.
| 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 if the block was set successfully, false otherwise.
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", "DUST") |
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 block or item data that is not supported by this simple API. BLOCK_CRACK, FALLING_DUST, BLOCK_DUST, and ITEM_CRACK will fail or produce no visible effect. Use data-free alternatives instead: CLOUD, SMOKE, CAMPFIRE_COSY_SMOKE, SNOWFLAKE, FLAME, DUST, etc.
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)
context.zones
The zones table lets you create spatial zones and watch them for player enter/leave events. Zones are tied to the script instance and cleaned up automatically when the script instance is destroyed.
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. When a player enters or leaves the zone, the corresponding callback fires (and, if defined, the on_zone_enter / on_zone_leave hooks).
| Parameter | Type | Notes |
|---|---|---|
handle | int | Zone handle from a create_* call |
onEnterCallback | function or nil | Called when a player enters the zone |
onLeaveCallback | function or nil | Called when a player leaves the zone |
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 -- or context.boss:get_location()
if loc == nil then return end
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, 5)
context.zones:watch(
handle,
function(player)
context.log:info("Player entered zone!")
end,
function(player)
context.log:info("Player left zone!")
end
)
context.state.zone_handle = handle
end,
on_destroy = function(context)
if context.state.zone_handle then
context.zones:unwatch(context.state.zone_handle)
end
end
}
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 |
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 |
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, event data, and zone callbacks. 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") |
is_valid | boolean | Whether the entity reference is still valid |
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. |
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 |
Not all entities returned by get_nearby_entities() are living entities. Always check if entity.damage then before calling living-entity methods like damage(), push(), or add_potion_effect().
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 |
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]) | void | Sends a fake block visible only to this player. If ticks is provided, the fake block auto-resets after that duration. |
reset_block(x, y, z) | void | Resets a fake block back to the real block for this 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 | required | 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 | Notes |
|---|---|---|
title | string | Main title text. Supports & color codes. |
subtitle | string | Subtitle text below the main title |
fade_in | int | Fade-in duration in ticks |
stay | int | How long the title stays on screen in ticks |
fade_out | int | Fade-out duration in ticks |
Next Steps
For plugin-specific hooks, APIs, and workflows:
- EliteMobs: Getting Started | Hooks & Lifecycle | Boss & Entities | World & Environment | Zones & Targeting
- FreeMinecraftModels: Getting Started | Prop & Item API | Examples