Skip to main content

Lua Scripting: Prop & Item API

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


context.prop

The prop table provides information about the prop entity and methods to control its animations. FMM caches this table per prop for performance; fields such as current_location are lazy/live, so reads still reflect the current prop state.

Fields

FieldTypeNotes
prop.model_idstringThe blueprint model name (e.g. "torch_01")
prop.current_locationlocation tableThe prop's current position. This is a live/lazy field, not a one-time snapshot.

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
}

prop:hurt_visual()

Plays the visual hurt animation (red tint flash) on the prop without dealing any actual damage to it.

Takes no parameters.

Example
return {
api_version = 1,

on_left_click = function(context)
-- Flash red when punched, but don't actually take damage
if context.event then
context.event.cancel()
end
context.prop:hurt_visual()
end
}

prop:pickup()

Removes the prop from the world and drops a placement paper item at its location. The dropped item can be right-clicked on a block to place the prop again.

Takes no parameters and returns nothing. The remove/drop work is queued onto the Bukkit main thread.

Example
return {
api_version = 1,

on_right_click = function(context)
-- Let players pick up the prop by right-clicking it
context.prop:pickup()
end
}

prop:mount(player)

Mounts a player onto the first available mount point seat on the prop. The model must have mount-point bones defined.

ParameterTypeNotes
playerentity tableA player entity table (e.g. from context.player or context.event.player)

Returns true when the player was found, the prop has mount points, and the mount action was queued. Returns false when those basic references are not valid. A true return does not prove a seat was ultimately available when the queued action ran.

Example
return {
api_version = 1,

on_right_click = function(context)
local player = context.event and context.event.player
if player then
context.prop:mount(player)
end
end
}

prop:dismount(player)

Dismounts a player from their mount point seat on the prop.

ParameterTypeNotes
playerentity tableA player entity table

Returns true when the player was found, the prop has a mount manager, and the dismount check was queued. Returns false when those basic references are not valid.

Example
return {
api_version = 1,

on_right_click = function(context)
local player = context.event and context.event.player
if player then
-- Toggle mount/dismount
local passengers = context.prop:get_passengers()
for i = 1, #passengers do
if passengers[i].uuid == player.uuid then
context.prop:dismount(player)
return
end
end
context.prop:mount(player)
end
end
}

prop:get_passengers()

Returns a Lua array of entity tables for all current passengers on the prop.

Takes no parameters.

Example
return {
api_version = 1,

on_game_tick = function(context)
local passengers = context.prop:get_passengers()
if #passengers > 0 then
context.log:info("Prop has " .. #passengers .. " passenger(s)")
end
end
}

prop:has_mount_points()

Returns whether this prop has mount-point bones defined in its model.

Takes no parameters. Returns true or false.

Example
return {
api_version = 1,

on_right_click = function(context)
local player = context.event and context.event.player
if player and context.prop:has_mount_points() then
context.prop:mount(player)
end
end
}

prop:spawn_elitemobs_boss(filename, x, y, z)

Spawns an EliteMobs custom boss at the given location. Requires EliteMobs to be installed on the server.

ParameterTypeNotes
filenamestringThe custom boss filename (e.g. "my_boss.yml")
xnumberX coordinate
ynumberY coordinate
znumberZ coordinate

Returns a living entity table for the spawned boss, or nil if EliteMobs is not installed or the boss file does not exist.

Example
return {
api_version = 1,

on_right_click = function(context)
local loc = context.prop.current_location
if loc then
local boss = context.prop:spawn_elitemobs_boss("dungeon_guardian.yml", loc.x, loc.y + 1, loc.z)
if boss then
context.log:info("Spawned boss: " .. (boss.name or "unknown"))
else
context.log:warn("Could not spawn boss -- is EliteMobs installed?")
end
end
end
}

prop:open_inventory(player, title, rows)

Opens a persistent chest inventory GUI for the player. Contents are saved to the prop's PersistentDataContainer when the inventory is closed, and restored when opened again.

ParameterTypeDefaultNotes
playerentity tablerequiredThe player to show the inventory to
titlestringrequiredThe inventory title (supports & color codes)
rowsint3Number of rows (1-6, where 6 = 54 slots = double chest)

Returns true if the prop/player references were valid and the open action was queued, false otherwise. Rows are clamped to the 1-6 range before the inventory is created.


prop:is_viewing_inventory(player)

Returns whether the given player currently has this prop's inventory open.

ParameterTypeNotes
playerentity tableThe player to check

Returns true or false.

Example: Close animation when inventory is closed
context.state["task_" .. player.uuid] = context.scheduler:run_repeating(5, 5, function(tick_context)
if not tick_context.prop:is_viewing_inventory(player) then
tick_context.prop:play_animation("close", true, false)
tick_context.scheduler:cancel(tick_context.state["task_" .. player.uuid])
end
end)

prop:place_book(player)

Takes the written or writable book from the player's main hand and stores it on the prop.

ParameterTypeNotes
playerentity tableThe player holding the book

Returns true if the prop/player references were valid and the action was queued. The later main-thread action only places a book if the player is holding a writable or written book and the prop does not already have one.


prop:read_book(player)

Opens the stored book for reading by the player.

ParameterTypeNotes
playerentity tableThe player to show the book to

Returns true if the prop/player references were valid and the read action was queued. It does not guarantee that a stored book existed.


prop:take_book(player)

Returns the stored book to the player's inventory and removes it from the prop.

ParameterTypeNotes
playerentity tableThe player to give the book to

Returns true if the prop/player references were valid and the take action was queued. It does not guarantee that a stored book existed.


prop:has_book()

Returns whether a book is stored on this prop. Takes no parameters.


prop:drop_inventory()

Drops all stored inventory contents at the prop's location as item entities, then clears the stored data. Automatically closes the inventory for any players currently viewing it.

Takes no parameters. Returns true if the prop has a valid backing armor stand and the drop action was queued. It does not guarantee that stored items existed.


prop:drop_book()

Drops the stored book at the prop's location as an item entity and clears the stored book data.

Takes no parameters. Returns true if the prop has a valid backing armor stand and the drop action was queued. It does not guarantee that a stored book existed.


prop:set_persistent_data(key, value)

Stores a string value on the prop's armor stand PersistentDataContainer. This data survives server restarts and chunk unloads.

ParameterTypeNotes
keystringA unique key name (stored under fmm_lua_<key> internally)
valuestringThe value to store. Use tostring() for numbers and booleans.

Returns true if successful, false if the prop has no backing armor stand.


prop:get_persistent_data(key)

Retrieves a string value previously stored with set_persistent_data. Returns nil if the key has not been set.

ParameterTypeNotes
keystringThe key name used in set_persistent_data
Example: Persistent toggle state
return {
api_version = 1,

on_spawn = function(context)
local saved = context.prop:get_persistent_data("active")
context.state.active = saved == "true"
end,

on_right_click = function(context)
context.state.active = not context.state.active
context.prop:set_persistent_data("active", tostring(context.state.active))
end
}

context.item

The item table is available in item scripts only (not prop scripts). It provides information about the custom item and methods to manipulate it. This table is rebuilt fresh for each hook call.

Item write methods such as set_amount, consume, set_uses, set_name, set_lore, and durability-use helpers queue their mutation onto the Bukkit main thread and return nil. Read methods return the currently equipped matching item state at the moment they run.

Fields

FieldTypeNotes
item.idstringThe item type ID (the fmm_item_id from the YML config)

item:material()

Returns the material name of the item as a string (e.g. "DIAMOND_SWORD", "STICK").


item:get_amount() / item:set_amount(n)

Gets or sets the stack size of the item. set_amount(n) queues the change and returns nil.

ParameterTypeNotes
nintThe new stack amount

item:consume(n)

Queues a decrement of the item's stack amount by n (default 1). If the resulting amount is 0 or less, the item is removed from the player's inventory. Returns nil.

ParameterTypeDefaultNotes
nint1Amount to consume

item:get_uses() / item:set_uses(n)

Gets or sets a custom use counter stored in the item's PersistentDataContainer. This is independent of vanilla durability and can be used to implement custom durability or charge systems. set_uses(n) queues the change and returns nil.

ParameterTypeNotes
nintThe new use count

item:get_name() / item:set_name(s)

Gets or sets the display name of the item. Supports color codes with &. set_name(s) queues the change and returns nil.

ParameterTypeNotes
sstringThe new display name (e.g. "&b&lFrost Sword")

item:get_lore() / item:set_lore(table)

Gets or sets the item's lore. get_lore() returns a table of strings (one per line). set_lore() takes a table of strings, queues the change, and returns nil.

ParameterTypeNotes
tabletableArray of strings, one per lore line
Example: Item script that tracks uses
return {
api_version = 1,

on_right_click = function(context)
local uses = context.item:get_uses()
if uses <= 0 then
context.player:send_message("&cThis item is out of charges!")
return
end
context.item:set_uses(uses - 1)
context.player:send_message("&aUsed! Charges remaining: " .. (uses - 1))
end
}

item:get_durability()

Returns a table with current and max fields representing the item's vanilla durability, or nil if the item has no durability bar.

Example
local dur = context.item:get_durability()
if dur then
context.player:send_message("Durability: " .. dur.current .. "/" .. dur.max)
end

item:get_durability_percentage()

Returns the remaining durability as a 0.0 to 1.0 fraction, or nil if the item has no durability bar.


item:use_durability(amount, can_break)

Queues a vanilla durability reduction by a flat amount and returns nil.

ParameterTypeDefaultNotes
amountintrequiredHow many durability points to consume
can_breakbooleanfalseIf true, the item is destroyed when durability runs out. If false, durability stops at 1.

item:use_durability_percentage(fraction, can_break)

Queues a vanilla durability reduction by a percentage of its maximum and returns nil.

ParameterTypeDefaultNotes
fractionnumberrequiredFraction of max durability to consume (e.g. 0.1 = 10%)
can_breakbooleanfalseIf true, the item is destroyed when durability runs out. If false, durability stops at 1.

context.event

Event data for the current hook. Available in click, combat, interaction, and generic zone hooks for prop and item scripts. Returns nil in hooks that have no associated event or player actor (on_spawn, on_game_tick, on_destroy, on_equip).

Hook reference tables below name the underlying Bukkit event type for orientation. The Lua wrapper still only exposes the fields and methods listed here.

Fields and Methods

Field or MethodTypeNotes
event.playerplayer entity tableThe player who triggered the event or crossed a watched generic zone boundary. Available in prop on_left_click, on_right_click, on_zone_enter, and on_zone_leave, plus item hooks that are caused by a player. See Player Entity Methods for all fields and methods.
event.is_cancelledbooleanCancellation state when the context was built. This field is not refreshed after calling cancel() or uncancel().
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, or if the hook is a generic zone hook without a Bukkit event behind it, event.cancel() and event.uncancel() will not be present and event.is_cancelled will always be false.

Treat event.is_cancelled as an initial-state snapshot. If your own script calls event.cancel() or event.uncancel(), keep your own local flag if you need to remember that change later in the same hook.

The current FMM event table does not expose Bukkit-specific fields such as target, block, projectile, or item. Use context.player, context.event.player, player:get_target_entity(range), context.world:raycast(...), or nearby-entity queries when you need extra context.

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

This is the FreeMinecraftModels/MagmaCore world API. See context.world for the full reference.

info

All methods documented on the global page (get_block_at, set_block_at, spawn_particle, play_sound, strike_lightning, get_time, set_time, get_nearby_entities, get_nearby_players, spawn_entity, get_highest_block_y, raycast, place_temporary_block, drop_item, spawn_firework) are available in FMM. See the MagmaCore world API for full details on world:raycast() (cast a ray and detect hit entities/blocks), world:place_temporary_block() (temporary block replacement), and world:spawn_firework() (spawn firework rockets with custom colors and shapes). EliteMobs boss powers start from the same world base and add boss-specific location-table methods for spawning bosses, reinforcements, falling blocks, temporary blocks, and more; see EliteMobs World & Environment.


Player Entity Methods

Player entity tables are returned from context.player, context.event.player, and context.world:get_nearby_players(). Generic MagmaCore on_zone_enter / on_zone_leave hooks set context.player and context.event.player to the entering or leaving player.

MagmaCore entity tables

The entity tables, living entity methods, player-specific methods, and Player UI methods documented on the global page are the MagmaCore tables used by FMM. EliteMobs boss powers expose similar but boss-specific entity tables documented in Boss & Entities. See the MagmaCore Lua Scripting Engine for the full FMM reference covering entity base fields, living entity fields and methods, player-specific fields and methods, and Player UI Methods. New player methods include player:get_target_entity() (raycast targeting), player:get_eye_location(), player:get_look_direction(), player:send_block_change() (per-player fake blocks), and player:reset_block() -- see Player-Specific Methods for details.

FMM-Specific Entity Fields

Every entity table built inside an FMM script gets these extra fields automatically (via FMM's LuaEntityEnricher):

FieldTypeNotes
entity.is_modeledbooleantrue if this Bukkit entity is the underlying entity of a ModeledEntity
entity.is_propbooleantrue if this entity is an armor stand backing a PropEntity
entity.modeltable or nilPopulated only when is_modeled = true (see below)

When entity.model is present, it exposes:

Field / MethodNotes
model.model_idThe blueprint model name (e.g. "dragon")
model.is_dynamictrue if this is a DynamicEntity (attached to a living entity)
model:play_animation(name, blend, loop)Plays a named animation. blend and loop default to false on the entity model bridge. Returns true on success
model:stop_animations()Stops all current animations
model:remove()Immediately removes the modeled entity and all its bones
on_right_click = function(context)
local player = context.event and context.event.player
if not player then return end

local target = player:get_target_entity(8)
if target and target.is_modeled then
target.model:play_animation("hurt", true, false)
end
end

EliteMobs Entity Fields

When EliteMobs is installed, FMM forwards to EliteMobs' enricher so the same entity tables also expose:

FieldTypeNotes
entity.is_elitebooleantrue if the entity is tracked by EliteMobs
entity.is_custom_bossbooleantrue if it is a custom boss configuration
entity.is_significant_bossbooleantrue for custom bosses with a healthMultiplier > 1 (filters out trash named mobs)
entity.elitetable or nilPopulated only when is_elite = true. Contains level, name, health, max_health, health_multiplier, damage_multiplier, is_custom_boss, plus elite:remove()

context.zones

This is the FreeMinecraftModels/MagmaCore zones API. See context.zones for the full reference. EliteMobs boss powers use a different context.zones table with native EliteMobs zone definitions; see EliteMobs Zones & Targeting.


context.scheduler

The scheduler API documented here is the MagmaCore scheduler used by FreeMinecraftModels scripts and EliteMobs NPC scripts. The EliteMobs-style names (run_after, run_every, and cancel_task) are aliases on the shared scheduler, so either naming style works. Boss powers expose the same aliases through their boss-specific context. See context.scheduler for the full FMM reference.


context.state

The state API is shared by FreeMinecraftModels scripts, EliteMobs boss powers, and EliteMobs NPC scripts. See context.state for the full reference.


context.log

The logging API documented here is the FreeMinecraftModels/MagmaCore logger (info, warn, error). EliteMobs NPC scripts use the same logger; EliteMobs boss powers expose info, warn, and debug. See context.log for the full reference.


context.cooldowns

The cooldown API documented here is the shared MagmaCore/FMM order used by FreeMinecraftModels scripts and EliteMobs NPC scripts: check_local(key?, duration). EliteMobs boss powers use the same argument order with boss-specific backing stores. See context.cooldowns for the full reference.

MethodNotes
local_ready(key?)Checks whether a local cooldown is ready.
local_remaining(key?)Returns remaining local cooldown ticks, or 0.
check_local(key?, duration)Checks and starts a local cooldown atomically.
set_local(duration, key?)Sets a local cooldown without checking.
global_ready()Checks the script owner's shared global cooldown.
set_global(duration)Sets the script owner's shared global cooldown.

Use context.cooldowns:check_local("my_key", 40) for normal prop or item action cooldowns.


Runtime Model

One Runtime Per Script Instance

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.

For item scripts, one runtime is created per (player, itemId) pair. When a player equips a custom item, FMM creates a script instance for that player and item type. When the item is unequipped, the runtime is shut down.

This means:

  • Local variables declared at file scope are private to that script instance.
  • context.state is completely isolated between 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.

Cooldown Scoping

The shared scripting engine exposes local cooldown helpers (local_ready, local_remaining, check_local, set_local) and global cooldown helpers (global_ready, set_global). FMM scopes those stores as follows:

Script typeLocal store scopeGlobal store scope
Prop scriptPer ScriptInstance (prop + script file)Per PropEntity (shared across every script bound to that prop)
Item scriptPer (player, itemId, scriptFile) triple — persists across re-equip even though the script instance is torn down each time the item leaves an active slotPer player (shared across every FMM item script that player runs)

Because item scripts are torn down and rebuilt on every equip/unequip cycle, item cooldowns are kept in a static map keyed by player UUID rather than living on the ScriptInstance. This is why an item cooldown still applies after you swap the item out of the hotbar and back in.

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.

Complete Hook Reference

This table lists all hooks available across both prop and item scripts.

The context.event column describes the underlying Bukkit event family. FMM's Lua event wrapper still only exposes event.player, event.is_cancelled, event.cancel(), and event.uncancel() where applicable.

Active Prop Hooks (7)

HookFires whencontext.event
on_spawnProp spawns into the worldnil
on_game_tickEvery server tick (50ms)nil
on_destroyProp is removednil
on_left_clickPlayer left-clicks the propdamage event
on_right_clickPlayer right-clicks the propinteraction event
on_zone_enterPlayer enters a watched zonezone player actor (context.player / context.event.player; not cancellable)
on_zone_leavePlayer leaves a watched zonezone player actor (context.player / context.event.player; not cancellable)
Reserved Prop Hook

The current script validator accepts on_projectile_hit for prop scripts, but the current runtime does not dispatch projectile hits to prop scripts yet. Use item on_projectile_hit for projectile behavior tied to a scripted item, or the Bukkit ModeledEntityHitByProjectileEvent API for plugin-side modeled-entity projectile handling.

Item Hooks (22)

HookCategoryFires whencontext.event
on_attack_entityCombatPlayer attacks an entitydamage event
on_kill_entityCombatPlayer kills an entitydeath event
on_take_damageCombatPlayer takes damagedamage event
on_shield_blockCombatPlayer blocks with shielddamage event
on_shoot_bowCombatPlayer shoots a bowbow shoot event
on_projectile_hitCombatPlayer's projectile hitsprojectile hit event
on_projectile_launchCombatPlayer launches projectilelaunch event
on_right_clickInteractionPlayer right-clicksinteract event
on_left_clickInteractionPlayer left-clicksinteract event
on_shift_right_clickInteractionPlayer shift+right-clicksinteract event
on_shift_left_clickInteractionPlayer shift+left-clicksinteract event
on_interact_entityInteractionPlayer right-clicks entityentity interact event
on_equipEquipmentItem enters active slotnil
on_unequipEquipmentItem leaves active slotnil
on_swap_handsEquipmentSwaps main/off handswap event
on_dropEquipmentPlayer drops itemdrop event
on_break_blockUtilityPlayer breaks blockblock break event
on_consumeUtilityPlayer consumes itemconsume event
on_item_damageUtilityItem takes durability damageitem damage event
on_fishUtilityPlayer uses fishing rodfish event
on_deathUtilityPlayer dies while equippeddeath event
on_game_tickLifecycleEvery tick while equippednil

Next Steps