Lua Scripting: Getting Started
This page teaches you to write your first Lua script for a FreeMinecraftModels prop, from an empty file all the way to a working interactive prop. By the end you will understand hooks, context, the prop API, and the general shape of every prop script file.
Once you are comfortable with the basics, continue with the companion pages:
- Prop API -- the
context.prop,context.event,context.world, and other context APIs - Examples & Patterns -- complete working scripts you can study and adapt
- Troubleshooting -- common errors, debugging tips, and the QC checklist
Lua prop scripts are currently experimental. Hook names, helper methods, and behavior can still change as FreeMinecraftModels evolves, so test carefully before using them on a production server.
FreeMinecraftModels shares the same Lua engine (Magmacore) as EliteMobs. If you already write Lua powers for EliteMobs, the core concepts -- hooks, context, api_version, scheduler, zones, and the sandbox -- are identical. The difference is:
- EliteMobs scripts run on bosses and have hooks like
on_boss_damaged_by_player,on_enter_combat, etc. - FMM scripts run on props and have hooks like
on_right_click,on_left_click,on_projectile_hit, etc.
The context.world, context.zones, context.scheduler, context.state, and context.log APIs are the same across both plugins. This page only covers what is specific to FMM props.
What Prop Scripts Are
Prop scripts are standalone .lua files that live in the plugins/FreeMinecraftModels/scripts/ folder. They are referenced from a YAML config file that sits alongside the model file, and they run whenever the prop is spawned into the world.
What Prop Scripts Are Good At
Prop scripts shine when you need:
- Interactive props that respond to player clicks (doors, levers, buttons)
- Invulnerable decorative props that cannot be broken by players
- Proximity triggers that detect when players enter or leave an area
- Animated props that play animations on interaction or on a timer
- Sound-emitting props that play sounds when clicked or approached
- Any prop behavior that requires logic beyond static decoration
If your prop is purely decorative and needs no interaction, you do not need a script.
Who This Page Is For
This page is written for three kinds of readers:
- Someone who already knows EliteMobs Lua scripting and wants to learn the FMM-specific hooks and APIs
- Someone who is new to Lua scripting and needs a complete, exact-name reference for props
- Someone using AI to draft prop scripts and needing enough detail to tell when the AI invented something fake
You do not need to become a full Lua developer before writing useful prop scripts. For most practical prop scripts, the only things you really need are:
- How to place a valid hook in the returned table
- How to read values from
context - How to stop early with
if ... then return end - How to call a few helper methods exactly
Tiny Lua Primer
If you are completely new to Lua, here is the minimum syntax you need.
Variables
Use local to store a value:
local animation_name = "open"
local sound_volume = 1.0
Functions
Functions are reusable blocks of logic:
local function log_click(context)
context.log:info("Prop was clicked!")
end
if checks
Use if when something should only happen sometimes:
if context.event == nil then
return
end
nil
nil means "no value". You will often check for it:
if context.event ~= nil then
context.event.cancel()
end
Tables
Lua uses tables for objects with named keys and for the returned script definition:
return {
api_version = 1,
on_spawn = function(context)
end
}
Comments
Use -- for notes:
-- Cancel the damage event so the prop cannot be broken
context.event.cancel()
For a more thorough Lua primer, see the EliteMobs Getting Started page. The syntax basics are identical.
Where Files Live
Script Files
Place .lua files in the central scripts folder:
plugins/
FreeMinecraftModels/
scripts/
invulnerable.lua
interactive_door.lua
proximity_sound.lua
FMM discovers all .lua files in plugins/FreeMinecraftModels/scripts/ at startup.
Model Files and Config Files
Each model file can have a sibling .yml config file in the same directory:
plugins/
FreeMinecraftModels/
models/
torch_01.fmmodel
torch_01.yml <-- script config for torch_01
scripts/
invulnerable.lua <-- referenced by torch_01.yml
The .yml config is what connects a model to its scripts.
Config File Format
The YAML config file that sits next to a model file has two fields:
isEnabled: true
scripts:
- invulnerable.lua
| Field | Type | Default | Notes |
|---|---|---|---|
isEnabled | boolean | true | Whether scripts are active for this prop |
scripts | list of strings | [] | Filenames of .lua scripts in the scripts/ folder |
You can attach multiple scripts to the same prop. Each script is its own independent instance.
Lazy Config Generation
When a prop spawns and no sibling .yml file exists, FMM automatically creates a default config file with isEnabled: true and an empty scripts: list. This happens asynchronously, so the prop will not have scripts on its first spawn -- only after the config is created and you edit it to add script filenames.
This means:
- Place your model file in
models/ - Spawn the prop once (FMM creates the
.ymlautomatically) - Edit the generated
.ymlto add your script filenames - Respawn the prop or reload (scripts are now active)
Hook Reference
Every Lua prop script file returns a table. Each key in that table (besides api_version and priority) must be one of the hooks listed below. The runtime calls the matching function whenever the corresponding game event fires.
| Hook | Fires when | Notes |
|---|---|---|
on_spawn | The prop is spawned into the world | Runs once when the script is bound |
on_game_tick | Once every server tick (50 ms) | Only active if the script defines this hook |
on_destroy | The prop is removed from the world | Cleanup hook |
on_left_click | A player left-clicks (hits) the prop | context.event is the damage event |
on_right_click | A player right-clicks the prop | context.event is the interaction event |
on_projectile_hit | A projectile hits the prop | context.event is the projectile hit event |
on_zone_enter | A player enters a watched zone | Requires a zone watch to be set up |
on_zone_leave | A player leaves a watched zone | Requires a zone watch to be set up |
Minimal File Contract
Every Lua prop script must return a table.
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 listed in the Hook Reference |
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.
Your First Working Prop Script, Built Slowly
Before Step 1: Set Up the Config
- Place your model file (e.g.
my_prop.fmmodel) inplugins/FreeMinecraftModels/models/ - Spawn the prop once to generate the
.ymlconfig - Create your script file in
plugins/FreeMinecraftModels/scripts/first_test.lua - Edit
plugins/FreeMinecraftModels/models/my_prop.yml:
isEnabled: true
scripts:
- first_test.lua
- Respawn the prop or reload the server
Step 1: Make the File Load
return {
api_version = 1,
on_spawn = function(context)
end
}
If this loads without errors in the console, you proved:
- The file is valid Lua
- FMM found it in the
scripts/folder - The config correctly references it
- The returned table shape is correct
Step 2: Make the Prop Do One Visible Thing
return {
api_version = 1,
on_spawn = function(context)
context.log:info("Prop script loaded for: " .. (context.prop.model_id or "unknown"))
end
}
Check the server console. If you see the log message, your hook is firing.
Step 3: React to a Player Click
return {
api_version = 1,
on_right_click = function(context)
context.log:info("Prop was right-clicked!")
end
}
Right-click the prop in-game. If the console shows the message, the click hook is working.
Step 4: Cancel Damage to Make the Prop Invulnerable
return {
api_version = 1,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}
This is the pattern used by the premade invulnerable.lua script. It cancels the damage event so the prop's backing armor stand cannot be destroyed.
Step 5: Play an Animation on Click
return {
api_version = 1,
on_right_click = function(context)
context.prop:play_animation("open", true, false)
end
}
This plays the "open" animation on the prop model, blended and non-looping.
What Is context?
Every hook function receives one argument called context. Think of it as a toolbox that FMM hands you each time something happens -- it contains everything you need to interact with the prop, the world, zones, and more.
on_right_click = function(context)
-- context.prop = the prop that was clicked
-- context.event = the click event (can be cancelled)
-- context.world = tools for particles, sounds, block queries
-- context.state = your own persistent storage
-- context.log = console logging
-- context.scheduler = delayed and repeating tasks
-- context.zones = spatial zone creation and watching
end
You don't create context yourself -- FMM creates it and passes it to your hook.
context is created fresh for each hook call, except for context.state which persists for the prop's entire lifetime. This means you can store data in context.state and read it back later in a different hook.
Key context APIs
Here is a summary of what is available. For full details, see Prop API.
-
context.prop-- The prop entity. Providesmodel_id,current_location,play_animation(), andstop_animation(). -
context.event-- The Bukkit event that triggered this hook. Available inon_left_click,on_right_click, andon_projectile_hit. Providescancel(),uncancel(), andis_cancelled. Isnilin hooks that have no event (likeon_spawnandon_game_tick). -
context.state-- A plain Lua table that persists for the prop's lifetime. Use it to store flags, toggle states, task IDs, and anything you need to remember between hooks. -
context.log-- Console logging withlog:info(msg),log:warn(msg), andlog:error(msg). -
context.scheduler-- Delayed and repeating tasks withscheduler:run_later(ticks, callback)andscheduler:run_repeating(delay, interval, callback). Callbacks receive a fresh context. Cancel tasks withscheduler:cancel(taskId). -
context.world-- World interaction: particles, sounds, block queries, lightning, nearby entities. See Prop API for full method list. -
context.zones-- Create and watch spatial zones (spheres, cylinders, cuboids). See Prop API for full method list.
Method Syntax: : vs .
In Lua, object:method(arg) is shorthand for object.method(object, arg). The FMM API accepts both forms:
context.log:info("hello")
context.log.info("hello") -- same thing
All documentation uses : consistently.
Copy-Paste Starter Templates
Smallest Valid Prop Script
return {
api_version = 1,
on_spawn = function(context)
end
}
Invulnerable Prop Template
return {
api_version = 1,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}
Interactive Prop Template
return {
api_version = 1,
on_spawn = function(context)
context.state.is_active = false
end,
on_right_click = function(context)
context.state.is_active = not context.state.is_active
if context.state.is_active then
context.prop:play_animation("activate", true, true)
else
context.prop:stop_animation()
end
end
}
Larger File Layout
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)
context.state.task_id = nil
end,
on_right_click = function(context)
do_something(context)
end,
on_destroy = function(context)
if context.state.task_id ~= nil then
context.scheduler:cancel(context.state.task_id)
end
end
}
First Real Workflow
When building a brand-new prop script, use this order:
- Create the
.luafile and makeon_spawnwork. - Add the script filename to the prop's
.ymlconfig. - Change to the actual hook you want (e.g.
on_right_click). - Add a log message first, before animations or effects.
- Add one real effect (animation, sound, particle).
- Only after that, add helpers, state, scheduler logic, or zones.
That order makes debugging dramatically easier because only one thing changes at a time.
Premade Scripts
FMM ships with one premade Lua script:
invulnerable.lua-- Cancels left-click damage events, making the prop indestructible. This is the simplest useful prop script.
You can find more examples on the Examples & Patterns page.
Lua Sandbox
Prop scripts run inside the same sandboxed LuaJ environment as EliteMobs. The sandbox restrictions are identical. For the full list of removed globals and available standard library functions, see the EliteMobs Hooks & Lifecycle page.
In summary:
- Removed:
debug,dofile,io,load,loadfile,luajava,module,os,package,require - Available: All
math.*,string.*,table.*functions,pairs,ipairs,type,tostring,tonumber,pcall,print, and more
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.
Next Steps
- Prop API -- full
context.prop,context.event,context.world,context.zones, andcontext.schedulerreference - Examples & Patterns -- complete working scripts with walkthroughs
- Troubleshooting -- common issues, debugging tips, and a QC checklist
If you are also writing EliteMobs Lua powers, the shared APIs (context.world, context.zones, context.scheduler, context.state, context.log) work identically. See the EliteMobs Lua documentation for the boss-specific APIs.