Skip to main content

Lua Scripting: Prop API

This page covers every API available to FreeMinecraftModels prop scripts: context.prop, context.event, context.world, context.zones, context.scheduler, context.state, and context.log. If you are new to prop scripting, start with Getting Started first.


context.prop

The prop table provides information about the prop entity and methods to control its animations. This is rebuilt fresh for each hook call.

Fields

FieldTypeNotes
prop.model_idstringThe blueprint model name (e.g. "torch_01")
prop.current_locationlocation tableThe prop's position at the time the context was built

The location table has the standard fields: x, y, z, world, yaw, pitch.

Example: reading prop info
return {
api_version = 1,

on_spawn = function(context)
context.log:info("Prop spawned: " .. (context.prop.model_id or "unknown"))
local loc = context.prop.current_location
if loc then
context.log:info("Location: " .. loc.x .. ", " .. loc.y .. ", " .. loc.z)
end
end
}

prop:play_animation(name, blend, loop)

Plays a named animation on the prop model.

ParameterTypeDefaultNotes
namestringrequiredThe animation name as defined in the model file
blendbooleantrueWhether to blend with the current animation
loopbooleantrueWhether the animation loops

Returns true if the animation was found and started, false otherwise.

Example
return {
api_version = 1,

on_right_click = function(context)
local success = context.prop:play_animation("open", true, false)
if not success then
context.log:warn("Animation 'open' not found on this model!")
end
end
}

prop:stop_animation()

Stops all currently playing animations on the prop.

Takes no parameters.

Example
return {
api_version = 1,

on_right_click = function(context)
context.prop:stop_animation()
end
}

context.event

Event data for the current hook. Available in on_left_click, on_right_click, and on_projectile_hit. Returns nil in hooks that have no associated event (on_spawn, on_game_tick, on_destroy, on_zone_enter, on_zone_leave).

Fields and Methods

Field or MethodTypeNotes
event.is_cancelledbooleanWhether the event is currently cancelled
event.cancel()functionCancels the event (e.g. prevents damage or interaction)
event.uncancel()functionUn-cancels a previously cancelled event
info

Not all events are cancellable. If the underlying Bukkit event does not implement Cancellable, event.cancel() and event.uncancel() will not be present and event.is_cancelled will always be false.

Example: Making a prop invulnerable

Example
return {
api_version = 1,

on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}

Example: Checking cancellation state

Example
return {
api_version = 1,

on_left_click = function(context)
if context.event and not context.event.is_cancelled then
context.event.cancel()
context.log:info("Damage cancelled!")
end
end
}
caution

Inside scheduled callbacks (scheduler:run_later, scheduler:run_repeating), context.event is always nil. Event modification can only happen during the event hook itself.


context.world

The world table provides methods for querying and interacting with the Minecraft world. It is built from the prop's current world.

Shared API

The context.world API is shared with EliteMobs. FMM props use the same Magmacore world table as EliteMobs bosses. The methods documented here are those available in the base Magmacore implementation. For the full reference, see the EliteMobs World & Environment page -- the methods listed there as part of the core context.world API are available in FMM as well.

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").

ParameterTypeNotes
xintBlock X coordinate
yintBlock Y coordinate
zintBlock Z coordinate
Example
return {
api_version = 1,

on_spawn = function(context)
local loc = context.prop.current_location
if loc then
local block = context.world:get_block_at(
math.floor(loc.x),
math.floor(loc.y) - 1,
math.floor(loc.z)
)
context.log:info("Prop is standing on: " .. block)
end
end
}

world:spawn_particle(particle, x, y, z, count, dx, dy, dz, speed)

Spawns particles at a location.

ParameterTypeDefaultNotes
particlestringrequiredBukkit Particle enum name, UPPER_CASE (e.g. "FLAME", "DUST")
xnumberrequiredX coordinate
ynumberrequiredY coordinate
znumberrequiredZ coordinate
countint1Number of particles
dxnumber0X spread/offset
dynumber0Y spread/offset
dznumber0Z spread/offset
speednumber0Particle speed
Example
return {
api_version = 1,

on_right_click = function(context)
local loc = context.prop.current_location
if loc then
context.world:spawn_particle("HEART", loc.x, loc.y + 1, loc.z, 5, 0.3, 0.3, 0.3, 0)
end
end
}

world:play_sound(sound, x, y, z, volume, pitch)

Plays a sound at a location.

ParameterTypeDefaultNotes
soundstringrequiredBukkit Sound enum name, UPPER_CASE (e.g. "ENTITY_EXPERIENCE_ORB_PICKUP")
xnumberrequiredX coordinate
ynumberrequiredY coordinate
znumberrequiredZ coordinate
volumenumber1.0Volume
pitchnumber1.0Pitch
Example
return {
api_version = 1,

on_right_click = function(context)
local loc = context.prop.current_location
if loc then
context.world:play_sound("BLOCK_NOTE_BLOCK_PLING", loc.x, loc.y, loc.z, 1.0, 1.2)
end
end
}

world:strike_lightning(x, y, z)

Strikes lightning at a location (visual and damaging).

ParameterTypeNotes
xnumberX coordinate
ynumberY coordinate
znumberZ coordinate
Example
return {
api_version = 1,

on_projectile_hit = function(context)
local loc = context.prop.current_location
if loc then
context.world:strike_lightning(loc.x, loc.y, loc.z)
end
end
}

world:get_time()

Returns the current world time in ticks.


world:set_time(ticks)

Sets the world time.

ParameterTypeNotes
ticksintWorld 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 living entities within a bounding box centered at the given coordinates.

ParameterTypeNotes
xnumberCenter X
ynumberCenter Y
znumberCenter Z
radiusnumberSearch radius (used as half-extent in all three axes)
Example
return {
api_version = 1,

on_right_click = function(context)
local loc = context.prop.current_location
if loc then
local entities = context.world:get_nearby_entities(loc.x, loc.y, loc.z, 10)
context.log:info("Found " .. #entities .. " entities nearby")
end
end
}

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.

ParameterTypeNotes
xnumberCenter X
ynumberCenter Y
znumberCenter Z
radiusnumberSearch radius
Example
return {
api_version = 1,

on_right_click = function(context)
local loc = context.prop.current_location
if loc then
local players = context.world:get_nearby_players(loc.x, loc.y, loc.z, 20)
for i = 1, #players do
context.log:info("Nearby player: " .. (players[i].name or "unknown"))
end
end
end
}

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 prop is removed.

zones:create_sphere(x, y, z, radius)

Creates a sphere zone centered at the given coordinates. Returns a numeric zone handle.

ParameterTypeNotes
xnumberCenter X
ynumberCenter Y
znumberCenter Z
radiusnumberSphere radius

zones:create_cylinder(x, y, z, radius, height)

Creates a cylinder zone. Returns a numeric zone handle.

ParameterTypeNotes
xnumberCenter X
ynumberCenter Y (base)
znumberCenter Z
radiusnumberCylinder radius
heightnumberCylinder height

zones:create_cuboid(x, y, z, xSize, ySize, zSize)

Creates a cuboid zone. Returns a numeric zone handle.

ParameterTypeNotes
xnumberCenter X
ynumberCenter Y
znumberCenter Z
xSizenumberHalf-extent in X
ySizenumberHalf-extent in Y
zSizenumberHalf-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 hook fires (on_zone_enter or on_zone_leave).

ParameterTypeNotes
handleintZone handle from a create_* call
onEnterCallbackfunction or nilCalled when a player enters the zone
onLeaveCallbackfunction or nilCalled when a player leaves the zone

Returns true if the watch was set up successfully, nil if the zone handle was invalid.

info

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.

ParameterTypeNotes
handleintZone handle to stop watching

Example: Proximity trigger zone

Example
return {
api_version = 1,

on_spawn = function(context)
local loc = context.prop.current_location
if loc == nil then return end

-- Create a sphere zone around the prop
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, 5)

-- Watch for player enter/leave
context.zones:watch(
handle,
function(player)
context.log:info("Player entered zone!")
end,
function(player)
context.log:info("Player left zone!")
end
)

-- Store handle so we can clean up later if needed
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
}

context.scheduler

The scheduler lets you run delayed and repeating tasks. All tasks are owned by the script instance and cancelled automatically when the prop is removed.

scheduler:run_later(ticks, callback)

Runs a callback once after a delay. Returns a numeric task ID.

ParameterTypeNotes
ticksintDelay in server ticks (20 ticks = 1 second)
callbackfunctionCalled with a fresh context when the delay expires
Example
return {
api_version = 1,

on_right_click = function(context)
context.log:info("Something will happen in 2 seconds...")

context.scheduler:run_later(40, function(delayed_context)
local loc = delayed_context.prop.current_location
if loc then
delayed_context.world:spawn_particle("EXPLOSION_EMITTER", loc.x, loc.y, loc.z, 1)
end
end)
end
}

scheduler:run_repeating(delay, interval, callback)

Runs a callback repeatedly at a fixed interval. Returns a numeric task ID.

ParameterTypeNotes
delayintInitial delay in ticks before the first run
intervalintTicks between each subsequent run
callbackfunctionCalled with a fresh context each interval
Example
return {
api_version = 1,

on_spawn = function(context)
-- Emit particles every second
context.state.particle_task = context.scheduler:run_repeating(0, 20, function(tick_context)
local loc = tick_context.prop.current_location
if loc then
tick_context.world:spawn_particle("FLAME", loc.x, loc.y + 1, loc.z, 3, 0.1, 0.1, 0.1, 0.02)
end
end)
end,

on_destroy = function(context)
if context.state.particle_task then
context.scheduler:cancel(context.state.particle_task)
end
end
}

scheduler:cancel(taskId)

Cancels a scheduled task by its ID.

ParameterTypeNotes
taskIdintThe task ID returned by run_later or run_repeating
Always cancel repeating tasks

If you start a repeating task, always cancel it in on_destroy (or when it is no longer needed). Forgetting to cancel leaves a background task running until the prop is removed from the server, which wastes performance and can cause errors.


context.state

A plain Lua table that persists for the prop'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
info

Only context.state persists between hook calls. All other context tables (context.prop, context.world, context.event, etc.) are rebuilt fresh each time.


context.log

Console logging methods. Messages are prefixed with the script filename in the server console.

MethodNotes
log:info(message)Informational message
log:warn(message)Warning message
log:error(message)Error-level message (functionally same as warn)
Example
return {
api_version = 1,

on_spawn = function(context)
context.log:info("Script loaded!")
end,

on_right_click = function(context)
context.log:warn("Prop was clicked -- this is a debug warning")
end
}

Runtime Model

One Runtime Per Prop

Every prop entity that has scripts attached gets its own independent Lua runtime instance. When the prop spawns, FMM loads the Lua source, evaluates it in a fresh sandboxed environment, and stores the returned table. When the prop is removed, the runtime is shut down.

This means:

  • Local variables declared at file scope are private to that prop instance.
  • context.state is completely isolated between prop instances, even if they share the same script file.

Scheduled Task Ownership

All tasks created through context.scheduler are owned by the runtime that created them. When a prop is removed:

  1. The runtime shuts down.
  2. Every owned task -- both one-shot and repeating -- is automatically cancelled.
  3. All zone watches are cleared.

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 handlers lightweight -- they run every single tick.
  • Use context.scheduler:run_repeating(...) to spread work across ticks.

Next Steps