Skip to main content

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 script. By the end you will understand hooks, context, the prop API, and the general shape of every 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 -- prop scripts with setup notes to study and adapt
  • Troubleshooting -- common errors, debugging tips, and the QC checklist
Experimental Feature

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.

Relationship To EliteMobs Lua

FreeMinecraftModels uses the MagmaCore Lua runtime. If you already write Lua powers for EliteMobs, the core concepts -- script files that return a table, api_version, hooks, context, state, cooldowns, scheduling, and the sandbox -- will feel familiar. The exact hooks and context method names still depend on the plugin:

  • EliteMobs scripts run on bosses and have hooks like on_boss_damaged_by_player, on_enter_combat, etc.
  • FMM prop scripts run on props and have hooks like on_right_click, on_left_click, on_zone_enter, etc.
  • Item enchantments use the separate authored enchantment context.

The context.world, context.zones, context.scheduler, context.state, and context.log APIs documented here are the FreeMinecraftModels/MagmaCore versions. EliteMobs NPC scripts use the same generic MagmaCore tables plus context.npc; EliteMobs boss powers use boss-specific variants for several tables. This page 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)
  • Decorative props that ignore default left-click damage
  • 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.


Custom items and enchantments​

Custom items still use a model-adjacent YAML file with a material, but their effects now use namespaced enchantments. The old equipped-item script runtime has been retired. A custom item with a nonempty scripts: list is skipped with an informational console message; its definition is left unchanged.

Recognized retired equipped-item Lua files in scripts/ are preserved under plugins/MagmaCore/outdated files/FreeMinecraftModels/<archive batch>/scripts/ before discovery and after imports. The archive rule requires a valid old script definition containing a retired item hook, such as on_equip or on_attack_entity. Prop scripts, files using only shared hooks, malformed scripts and current effects in enchantments/ are not classified as retired item scripts by this rule. See retired configuration files for recovery guidance.

Use custom items and magic weapons for current item definitions and authored enchantments for effects. Prop scripts: lists continue to work.

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​

You don't need to be a Lua expert to write FMM scripts. Most scripts only use a handful of concepts: variables (local x = 5), functions (function foo() end), if checks (if x then ... end), tables ({key = value}), and nil (Lua's "nothing" value). The syntax is lightweight — no semicolons, no curly braces, just end to close blocks.

For a complete walkthrough with examples, see the MagmaCore Lua Scripting Engine — Tiny Lua Primer. That primer is shared across all Nightbreak plugins, so learning it once applies everywhere.


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 .lua files recursively in plugins/FreeMinecraftModels/scripts/ when initializing its script provider, then caches their source. Keep filenames unique across subfolders because bindings use the filename, not its relative path. After adding or editing Lua files, complete a successful /fmm reload before creating new bindings. Respawning alone does not reread Lua source.

In a model's scripts: list, include the .lua extension for clarity. FMM also accepts entries without the extension and appends .lua internally. The file on disk must still end in .lua, and names remain case-sensitive.

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 these fields:

isEnabled: true
voxelize: false
solidify: false
scripts:
- invulnerable.lua

The prop fields are:

FieldTypeDefaultNotes
isEnabledbooleantrueWhether the definition is enabled
scriptslist of strings[]Prop Lua filenames from scripts/
voxelizebooleanfalseSnap placement to the block grid and 90-degree rotation
solidifybooleanfalseAdd packet barrier blocks to a voxelized prop

Each listed prop script gets its own instance. For held items, define material, name, lore, and namespaced enchantments instead. See custom items.

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:

  1. Place your model file in models/
  2. Spawn the prop once (FMM creates the .yml automatically)
  3. Edit the generated .yml to add your script filenames
  4. Complete a successful /fmm reload after editing either YAML or Lua, then place a fresh prop and confirm its own log or interaction. Prop settings, placement checks and menu script lists share the prepared catalog; respawning alone does not reread the YAML.

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.

HookFires whenNotes
on_spawnThe prop is spawned into the world, or loads again with its chunkRuns once each time the script is bound
on_game_tickOnce every server tick (50 ms at 20 TPS)Only active if the script defines this hook
on_destroyThe prop is removed for goodCleanup hook. Not called when the prop's chunk unloads or FMM reloads
on_left_clickA player left-clicks (hits) the propcontext.event carries FMM's default-damage cancellation flag
on_right_clickA player right-clicks the propcontext.event carries FMM's default-interaction cancellation flag
on_zone_enterA player enters a watched zoneRequires a zone watch to be set up
on_zone_leaveA player leaves a watched zoneRequires a zone watch to be set up
on_projectile_hitA projectile hits the propcontext.projectile is the projectile; context.event.player is the shooter. See Prop API

Item effects​

The former equipped-item hooks are no longer dispatched. Effects belong to authored enchantments, which have their own supported hooks and context. Do not attach enchantment Lua to a prop's scripts: list or copy an old item script unchanged.

Minimal File Contract​

Every Lua prop script must return a table.

Required and Optional Top-Level Fields​

FieldRequiredTypeNotes
api_versionYesNumberCurrently must be 1
priorityNoNumberValidated if present, but FMM does not currently sort prop scripts by it. Props run in scripts: list order
supported hook keysNoFunctionMust use one of the exact hook names listed in the Hook Reference

Validation Rules​

  • The file must return a table.
  • api_version is required and must currently be 1.
  • priority must 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.
note

priority is useful to keep scripts portable across MagmaCore-based runtimes, but FreeMinecraftModels' current runtime order is config-driven. Put prop scripts in the order you want them to run in the model's scripts: list.

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​

  1. Place your model file (e.g. my_prop.fmmodel) in plugins/FreeMinecraftModels/models/
  2. Spawn the prop once to generate the .yml config
  3. Create your script file in plugins/FreeMinecraftModels/scripts/first_test.lua
  4. Edit plugins/FreeMinecraftModels/models/my_prop.yml:
isEnabled: true
scripts:
- first_test.lua
  1. Save the Lua content from Step 1 below, run /fmm reload, and check that it succeeds. Respawn the prop if needed. Repeat the successful reload after each Lua edit in the following steps.

Step 1: Make the File Load​

return {
api_version = 1,

on_spawn = function(context)
end
}

No console error is a useful first check. Use the explicit log in Step 2 to confirm that:

  • 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 Left-Click Damage​

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 FMM's default prop damage for that click. It does not make the backing entity invulnerable to every damage source.

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 requests the non-looping "open" animation after the current state tick. The model must contain that animation. The blend argument does not cross-fade; the queue has one slot and a later request can replace it. Terminal death can reject the request. Check the visible result in game; acceptance alone does not prove playback.


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.

You don't create context yourself -- FMM creates it and passes it to your hook. For full details on the shared context APIs (context.state, context.log, context.cooldowns, context.scheduler, context.world, context.zones), see the MagmaCore Lua Scripting Engine page.


Key context APIs​

Here is a summary of what is available. For full details, see Prop API.

  • context.prop -- (Prop scripts only) The prop entity. Provides model_id, current_location, play_animation(), and stop_animation().

  • context.player -- The triggering player in prop click hooks and generic zone hooks, and the shooter in on_projectile_hit when a player fired the projectile. It is nil in prop lifecycle hooks, scheduled callbacks, and hooks that do not involve a player.

  • context.event -- FMM's cancellation flag and player actor for click hooks, the projectile-hit event and its shooter in on_projectile_hit, or an actor-only table for generic zone hooks. Provides event.player, is_cancelled, and, for clicks and projectile hits, cancel() / uncancel(); it does not expose Bukkit-specific fields such as target, block, projectile, or item. Is nil in hooks that have no event or player actor (like on_spawn, on_game_tick).

  • context.projectile -- The projectile entity in on_projectile_hit, and nil in every other hook. See on_projectile_hit.

  • context.state -- A plain Lua table that persists for the script instance's lifetime. See context.state.

  • context.cooldowns -- Local and global cooldown helpers. Use context.cooldowns:check_local("key", ticks) for normal per-script cooldowns. See context.cooldowns.

  • context.log -- Console logging. See context.log.

  • context.scheduler -- Delayed and repeating tasks. See context.scheduler.

  • context.world -- World interaction: particles, sounds, block queries, lightning, nearby entities. See context.world.

  • context.zones -- Create and watch spatial zones (spheres, cylinders, cuboids). See context.zones.


Method Syntax: : vs .​

For an explanation of : vs . method syntax in Lua, see the MagmaCore Lua Scripting Engine page. Both forms are accepted by the FMM API.


Copy-Paste Starter Templates​

Smallest Valid Prop Script​

return {
api_version = 1,

on_spawn = function(context)
end
}

Left-Click Damage Cancellation 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:

  1. Create the .lua file and make on_spawn work.
  2. Add the script filename to the prop's .yml config.
  3. Change to the actual hook you want (e.g. on_right_click).
  4. Add a log message first, before animations or effects.
  5. Add one real effect (animation, sound, particle).
  6. Only after that, add helpers, state, scheduler logic, or zones.

After each Lua edit, save the file and complete a successful /fmm reload before checking the new behavior. Keep one change per step so the log or visible effect identifies what worked.


Premade Scripts​

FMM writes four premade Lua scripts when they are missing. It also replaces recognized older default sources, but preserves other existing edits. Inspect the installed file when its behavior differs from the current defaults:

  • invulnerable.lua -- Cancels FMM's default left-click damage. It does not block every possible damage source.
  • pickupable.lua -- Cancels default left-click damage and counts three permitted hits before requesting pickup. The counter belongs to the binding, so players share it; a 100-server-tick task resets it after an earlier hit. Permitted hits request a hurt flash. The script blocks protected or dungeon locations unless the player has the FMM protection-bypass permission or operator status.
  • storage_double.lua -- Requests a 54-slot inventory, open/close animations and sounds. It applies the same protection/operator bypass check and polls viewer tracking every 5 server ticks. The prop's inventory is shared by every viewer and saved when the last viewer closes it or the chunk unloads. Destruction requests drop_inventory(), which saves the open inventory first, so items placed in a still-open inventory are included in the drop. See prop:open_inventory for the details.
  • storage_single.lua -- Same as storage_double but with 3 rows (27 slots) instead of 6.

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 MagmaCore Lua Scripting Engine page.


Next Steps​

  • Prop API -- full context.prop, context.event, context.world, context.zones, and context.scheduler reference
  • Examples & Patterns -- prop scripts with walkthroughs and setup notes
  • Troubleshooting -- common issues, debugging tips, and a QC checklist

If you are also writing EliteMobs boss Lua powers, the sandbox, api_version, state table, cooldown concepts, and hook-driven structure are familiar, but the boss context uses EliteMobs-specific method names. See the EliteMobs Lua documentation for the exact boss APIs.