跳至主要内容

Lua Scripting: Getting Started

webapp_banner.jpg

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, and context.entities APIs
  • World & Environment -- particles, sounds, lightning, spawning, and the context.world API
  • Zones & Targeting -- native Lua zones, Script Utilities targeting, and the context.zones / context.script APIs
  • 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
Experimental Feature

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.

Relationship To EliteScript

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 termsIn Lua that usually means
EventsHook names such as on_spawn or on_boss_damaged_by_player
Cooldownscontext.cooldowns (see context.cooldowns below)
ActionsDirect method calls such as context.world:spawn_particle_at_location(...) or context.script:damage(...)
Targetscontext.script:target({...}) -- uses EliteScript Target field names
Zonescontext.script:zone({...}) or native context.zones helpers -- see Zones & Targeting
Relative vectorscontext.script:relative_vector({...}) or context.vectors
Script flowYour 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:

  1. Save the file in plugins/EliteMobs/powers/
  2. Add the .lua filename to the boss powers: list
  3. Spawn or reload that boss
  4. 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_spawn is 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_player is the hook name
  • context.player is the player involved in that hook
  • return exits 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:

  1. Create the file and make on_spawn work.
  2. Change to the actual hook you want.
  3. Confirm the hook has the data you expect, such as context.player or context.event.
  4. Add a message or sound first, before damage or particles.
  5. Add cooldowns.
  6. Add one gameplay effect.
  7. 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.lua and powers/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

FieldRequiredTypeNotes
api_versionYesNumberCurrently must be 1
priorityNoNumberExecution priority. Lower values run first. Defaults to 0
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.

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
}
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. Only context.state persists — all other context tables are created fresh each call.

  • context.log — Console logging with log:info(msg), log:warn(msg), and log:debug(msg). Invaluable during development.

  • context.cooldowns — Per-power local cooldowns and per-boss global cooldowns. The key method is cooldowns: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 with scheduler:run_after(ticks, callback) and scheduler:run_every(ticks, callback). Callbacks receive a fresh context — always use the callback parameter, not the outer context. Cancel repeating tasks in on_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, and context.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: