Lua Scripting: Getting Started
What You'll Learn
This page teaches you to write your first Lua power for EliteMobs, from an empty file all the way to a working boss ability. By the end you will understand hooks, context, cooldowns, and the general shape of every Lua power file.
Once you are comfortable with the basics, continue with the companion pages:
- Hooks & Lifecycle -- hook names, firing order, and context availability
- Boss & Entities -- the
context.boss,context.player,context.players, andcontext.entitiesAPIs - World & Environment -- particles, sounds, lightning, spawning, and the
context.worldAPI - Zones & Targeting -- native Lua zones, Script Utilities targeting, and the
context.zones/context.scriptAPIs - Examples & Patterns -- complete working powers you can study and adapt
- Enums & Values -- links to Spigot Javadocs for Particle, Material, PotionEffectType, and other string constants
- Troubleshooting -- common errors, debugging tips, and the QC checklist
Lua power files are currently experimental. Hook names, helper methods, and behavior can still change as EliteMobs evolves, so test carefully before using them on a production server.
Lua does not replace the existing eliteScript: YAML system.
- Use EliteScript when you want declarative, YAML-driven scripting with the existing Actions, Targets, Zones, Conditions, Cooldowns, and Relative Vectors pages.
- Use Lua power files when you need variables, loops, random selection, reusable helper functions, persistent per-boss state, and more traditional programming flow.
Lua has its own targeting and zone system that uses the same familiar enum names and concepts you already know from EliteScript. The Script Utilities API (context.script) lets you create zones, targets, and relative vectors with the same field names documented in the EliteScript pages.
What Lua Powers Are
Lua powers are standalone .lua files that live in the normal EliteMobs powers tree and are referenced exactly like normal power files.
What Lua Powers Are Good At
Lua powers shine when you need:
- Attack rotations and random move pickers
- Persistent state between hooks with
context.state - Delayed and repeating actions without building everything out of YAML waits
- Custom helper functions shared across one file
- Complex branching that would be awkward in pure EliteScript
- Boss logic that still wants to reuse EliteScript-style targeting and zone definitions
If your power is mostly "trigger event, do a few scripted actions", the existing EliteScript docs are still the fastest and clearest way to build it. If your power needs real program flow, Lua is the tool for that job.
Who This Page Is For
This page is written for three kinds of readers:
- Someone who already knows EliteScript and wants to learn Lua without learning "real programming" all at once
- Someone who is new to EliteMobs scripting and needs a complete exact-name reference
- Someone using AI to draft powers 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 powers. For most practical EliteMobs powers, 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
- How to copy the right target and zone specs from the existing EliteScript docs
Mental Model: EliteScript vs Lua
If you know EliteScript, this comparison is the fastest way to understand Lua powers:
| If you think in EliteScript terms | In Lua that usually means |
|---|---|
| Events | Hook names such as on_spawn or on_boss_damaged_by_player |
| Cooldowns | context.cooldowns (see context.cooldowns below) |
| Actions | Direct method calls such as context.world:spawn_particle_at_location(...) or context.script:damage(...) |
| Targets | context.script:target({...}) -- uses EliteScript Target field names |
| Zones | context.script:zone({...}) or native context.zones helpers -- see Zones & Targeting |
| Relative vectors | context.script:relative_vector({...}) or context.vectors |
| Script flow | Your own Lua if, helper functions, timers, and state |
The biggest difference is this:
- EliteScript says what should happen in YAML.
- Lua lets you decide when, why, and which branch should happen using code.
So if EliteScript feels like "configuration", Lua feels like "configuration plus decision-making".
Tiny Lua Primer For EliteMobs Authors
This is the minimum Lua syntax most power authors need.
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(context.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 power definition
Example of a table with named keys:
local particle = {
particle = "FLAME",
amount = 1,
speed = 0.05
}
Returning the power definition
At the end of the file, you return one table:
return {
api_version = 1,
on_spawn = function(context)
end
}
That returned table is the power 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")
Your First Working Power, Built Slowly
If you are completely new, this is the best "first win" progression.
Before Step 1: Save It and Attach It to a Boss
Save the Lua file in the normal EliteMobs powers folder, for example:
plugins/
EliteMobs/
powers/
first_test.lua
Then add that filename to a boss config using the normal powers: list:
powers:
- first_test.lua
So the full beginner loop is:
- Save the file in
plugins/EliteMobs/powers/ - Add the
.luafilename to the bosspowers:list - Spawn or reload that boss
- Test the hook you are currently building
If you need more background on boss files, power lists, or how custom bosses are structured, see Creating Custom Bosses. That page covers boss setup in detail, including how the powers: list works.
Step 1: Make the File Load
return {
api_version = 1,
on_spawn = function(context)
end
}
If this loads without errors, you already proved:
- The file is valid Lua
- EliteMobs found it
- The returned table shape is correct
on_spawnis a valid hook name
Step 2: Make the Boss Do One Visible Thing
return {
api_version = 1,
on_spawn = function(context)
context.boss:play_sound_at_self("entity.blaze.ambient", 1.0, 1.0)
end
}
Now you have the most important beginner proof: your hook is firing.
Step 3: React to a Player Hit
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end
context.player:send_message("&eYou hit the boss.")
end
}
This teaches three core ideas:
on_boss_damaged_by_playeris the hook namecontext.playeris the player involved in that hookreturnexits early when the data you need is missing
Step 4: Prevent Spam With a Cooldown
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end
if not context.cooldowns:local_ready("hello_message") then
return
end
context.player:send_message("&eYou woke up the boss.")
context.cooldowns:set_local(60, "hello_message")
end
}
This is the first genuinely useful pattern most authors need. If you understand this pattern, you can already build many practical powers.
Step 5: Add One Real Effect
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end
if not context.cooldowns:local_ready("shock") then
return
end
context.player:send_message("&cStatic jumps from the boss into your armor!")
context.world:strike_lightning_at_location(context.player.current_location)
context.cooldowns:set_local(100, "shock")
end
}
At this point, you are already writing a real power.
First Real Workflow
When building a brand-new Lua power, use this order:
- Create the file and make
on_spawnwork. - Change to the actual hook you want.
- Confirm the hook has the data you expect, such as
context.playerorcontext.event. - Add a message or sound first, before damage or particles.
- Add cooldowns.
- Add one gameplay effect.
- Only after that, add helpers, state, scheduler logic, or zones.
That order makes debugging dramatically easier because only one thing changes at a time.
Where Lua Files Live
Place .lua files in the same folder tree as normal power .yml files:
plugins/
EliteMobs/
powers/
mycoolpower.lua
attack_push.yml
subfolder/
myotherpower.lua
Lua powers are auto-discovered from the power directories EliteMobs already loads.
How Bosses Reference Lua Powers
Boss files still use the normal powers: list:
powers:
- attack_push.yml
- mycoolpower.lua
No special field is required. Lua powers are not loaded through eliteScript:.
File Naming Rules
- Bosses reference Lua powers by filename, not by folder path.
- EliteMobs currently registers discovered Lua powers by basename only.
- Avoid duplicate names like
powers/fire.luaandpowers/bosses/fire.lua, because one can overwrite the other during discovery.
Premade Lua Powers
EliteMobs ships with dozens of premade Lua powers in the powers folder, such as attack_fire.lua, frost_cone.lua, meteor_shower.lua, and many more. These are excellent references to study — just open any .lua file in your plugins/EliteMobs/powers/ directory. For backwards compatibility, premade powers are also registered under their legacy .yml names.
Minimal File Contract
Every Lua power 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 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.
This means helper functions and local constants should live above the final return, not inside the returned table unless they are actual hooks.
Copy-Paste Starter Templates
Smallest Valid Lua Power
return {
api_version = 1,
on_spawn = function(context)
end
}
Recommended Starter Template
local ATTACK_COOLDOWN = "my_attack"
local function can_run_attack(context)
return context.cooldowns:local_ready(ATTACK_COOLDOWN)
and context.cooldowns:global_ready()
end
local function run_attack(context)
context.boss:play_sound_at_self("entity.blaze.shoot", 1.0, 1.0)
context.cooldowns:set_local(100, ATTACK_COOLDOWN)
context.cooldowns:set_global(20)
end
return {
api_version = 1,
priority = 0,
on_boss_damaged_by_player = function(context)
if context.player == nil then
return
end
if not can_run_attack(context) then
return
end
run_attack(context)
end
}
Larger File Layout
local CONSTANT_NAME = "value"
local function helper_function(context)
end
local function another_helper(context, value)
end
return {
api_version = 1,
priority = 0,
on_spawn = function(context)
end,
on_enter_combat = function(context)
end,
on_boss_damaged_by_player = function(context)
end,
on_exit_combat = function(context)
end
}
Hooks
Hooks are specially named functions in the table you return. EliteMobs calls them when something happens — the boss spawns, takes damage, enters combat, etc. You already saw on_spawn and on_boss_damaged_by_player in the tutorial above.
The most common hooks to start with are on_spawn, on_boss_damaged_by_player, on_enter_combat, and on_exit_combat. For the full list of all hooks, which context keys are available in each, and how execution order works, see Hooks & Lifecycle.
What Is context?
Every hook function receives one argument called context. Think of it as a toolbox that EliteMobs hands you each time something happens — it contains everything you need to interact with the boss, the player, the world, cooldowns, and more.
on_boss_damaged_by_player = function(context)
-- context.boss = the boss that was hit
-- context.player = the player who hit it
-- context.world = tools for spawning particles, sounds, etc.
-- context.cooldowns = cooldown management
-- context.state = your own persistent storage
end
You don't create context yourself — EliteMobs creates it and passes it to your hook. You just read from it and call methods on it.
context is created fresh for each hook call, except for context.state which persists for the boss'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 are the most important APIs you'll use from context:
-
context.state— A plain Lua table that persists for the boss's lifetime. Use it to store phase numbers, task IDs, flags, and anything you need to remember between hooks. Onlycontext.statepersists — all other context tables are created fresh each call. -
context.log— Console logging withlog:info(msg),log:warn(msg), andlog:debug(msg). Invaluable during development. -
context.cooldowns— Per-power local cooldowns and per-boss global cooldowns. The key method iscooldowns:check_local(key, ticks)which checks AND sets a cooldown atomically. See the Hooks & Lifecycle page for the full cooldown API. -
context.scheduler— Delayed and repeating tasks withscheduler:run_after(ticks, callback)andscheduler:run_every(ticks, callback). Callbacks receive a fresh context — always use the callback parameter, not the outercontext. Cancel repeating tasks inon_exit_combat. See Hooks & Lifecycle for details. -
context.boss/context.player— The boss and player involved in the current event. See Boss & Entities for all fields and methods. -
context.world— Spawning particles, entities, sounds, lightning, blocks. See World & Environment. -
context.zones/context.script— Zone geometry, targeting, damage, particles. See Zones & Targeting.
Method Syntax: : vs .
In Lua, object:method(arg) is shorthand for object.method(object, arg). The EliteMobs API accepts both forms, so either works:
context.cooldowns:set_local(60, "test")
context.cooldowns.set_local(60, "test") -- same thing
All documentation uses : consistently.
Next Steps
Now that you know the basics, explore the rest of the Lua scripting documentation:
- Hooks & Lifecycle -- hook names, firing order, and which context keys are available in each hook
- Boss & Entities --
context.boss,context.player,context.players,context.entities, andcontext.event - World & Environment -- particles, sounds, lightning, spawning, and
context.world - Zones & Targeting -- native Lua zones, Script Utilities targeting/zones/particles, and relative vectors
- Examples & Patterns -- complete working powers with walkthroughs
- Enums & Values -- links to Spigot Javadocs for Particle, Material, PotionEffectType, and more
- Troubleshooting -- common errors, debugging tips, and a QC checklist
For YAML-based scripting, the EliteScript pages remain the canonical reference:
