Saltar al contenido principal

Scripting Lua: Hooks y Ciclo de Vida

webapp_banner.jpg

Esta página cubre cada hook que un poder Lua puede definir, el orden en que se ejecutan los hooks, cómo cada jefe obtiene su propio entorno de ejecución aislado y qué funciones de la biblioteca estándar están disponibles dentro del sandbox.

Si aún no has escrito un poder Lua, comienza primero con Primeros Pasos.


Referencia de Hooks

Every Lua power 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 whencontext.player available?
on_spawnThe elite mob spawnsNo
on_game_tickOnce every server tick (50 ms) while the runtime clock is activeNo
on_boss_damagedThe boss takes damage from any sourceNo
on_boss_damaged_by_playerThe boss takes damage from a playerYes
on_boss_damaged_by_eliteThe boss takes damage from another elite mobNo
on_player_damaged_by_bossA player takes damage from this bossYes
on_enter_combatThe boss enters combatYes
on_exit_combatThe boss leaves combatNo
on_healThe boss healsNo
on_boss_target_changedThe boss switches its targetYes
on_deathThe boss diesNo
on_phase_switchA phase boss switches to a new phaseNo
on_zone_enterAn entity enters a watched zoneYes (if the entity is a player)
on_zone_leaveAn entity leaves a watched zoneYes (if the entity is a player)

When context.player is listed as "No", accessing it returns nil. Always nil-check before using it.

Poder típico con múltiples hooks

A single Lua power can define as many hooks as it needs. Below is a skeleton that uses three hooks together:

return {
api_version = 1,

on_enter_combat = function(context)
-- Initialize per-fight state when combat begins
context.state.hit_count = 0
context.log:info("Combat started!")
end,

on_boss_damaged_by_player = function(context)
-- Track hits and trigger an ability every 5th hit
context.state.hit_count = (context.state.hit_count or 0) + 1
if context.state.hit_count % 5 ~= 0 then
return
end
if not context.cooldowns.check_local("counter_attack", 100) then
return
end
-- Fire a projectile back at the player
local origin = context.boss:get_location()
origin:add(0, 1, 0)
context.boss:summon_projectile(
"SMALL_FIREBALL", origin, context.player:get_location(), 1.5
)
end,

on_death = function(context)
-- Spawn a firework on death
context.world:spawn_particle_at_location(
context.boss:get_location(), "EXPLOSION_EMITTER", 1
)
end
}

Datos del Evento (context.event)

Algunos hooks reciben una tabla context.event que expone datos sobre el evento del juego que activó el hook. Los campos disponibles dependen de qué hook se está ejecutando.

Hooks de Daño

Aplica a on_boss_damaged, on_boss_damaged_by_player, on_boss_damaged_by_elite y on_player_damaged_by_boss.

Field / MethodTypeDescription
event.damage_amountdoubleRaw damage value
event.damage_causestringSpigot DamageCause name (e.g. "ENTITY_ATTACK", "PROJECTILE")
event.damagerentity tableEntity that dealt the damage. Only present on damage-by-entity hooks.
event.projectileentity tableThe projectile entity, if the damager was a projectile.
event.set_damage_amount(n)Override the damage to a fixed value
event.multiply_damage_amount(n)Multiply the current damage by n
event.cancel_event()Cancel the damage event entirely
on_boss_damaged_by_player = function(context)
-- Halve all projectile damage
if context.event.damage_cause == "PROJECTILE" then
context.event.multiply_damage_amount(0.5)
end
end

Hook de Spawn

Aplica a on_spawn.

Field / MethodTypeDescription
event.spawn_reasonstringSpigot SpawnReason name
event.cancel_event()Cancel the spawn

Hook de Muerte

Aplica a on_death.

Field / MethodTypeDescription
event.entityentity tableThe dying entity

Hooks de Zona

Aplica a on_zone_enter y on_zone_leave.

Field / MethodTypeDescription
event.entityentity tableThe entity entering or leaving the zone

Eventos Cancelables (General)

Cualquier hook cuyo evento subyacente del juego sea cancelable expone event.cancel_event(). Si context.event es nil para un hook dado (ej. on_game_tick, on_heal), no hay evento subyacente con el que interactuar.

Para los campos completos de la tabla de entidades, consulta Jefe y Entidades. Para los valores de causa de daño y razón de spawn, consulta Enums y Valores.


Orden de ejecución de hooks

When a boss has multiple Lua powers attached, each power's hook is called for the same event. The order is determined by the priority field:

  • Lower values run first (default is 0).
  • Powers with the same priority run in load order (effectively unspecified).
return {
api_version = 1,
priority = -10, -- runs before most other powers

on_boss_damaged_by_player = function(context)
-- This runs early, so other powers see any state changes we make
context.state.last_attacker = context.player.uuid
end
}

Priority only affects ordering among Lua powers on the same boss. It does not interact with EliteScript execution order.


Modelo de ejecución

Un entorno de ejecución por jefe

Every boss entity gets its own independent Lua runtime instance. When the boss spawns, EliteMobs loads the Lua source, evaluates it in a fresh sandboxed environment, and stores the returned table. When the boss despawns or is removed, the runtime is shut down.

This means:

  • Global Lua variables set during file evaluation (like helper functions with local function) are private to that boss.
  • The returned table's hook functions are never shared between bosses.

Aislamiento de estado

Each runtime has its own context.state table. One boss's state is completely invisible to every other boss, even if they share the same Lua power file. Use context.state to store counters, flags, timers, or any per-boss data you need across hooks.

return {
api_version = 1,

on_boss_damaged_by_player = function(context)
-- Each boss tracks its own enrage counter independently
context.state.enrage_hits = (context.state.enrage_hits or 0) + 1
if context.state.enrage_hits >= 20 then
context.boss:apply_potion_effect("SPEED", 200, 2)
end
end
}

Propiedad de tareas programadas

All tasks created through context.scheduler are owned by the runtime that created them. When a boss despawns:

  1. The runtime calls shutdown().
  2. Every owned task -- both one-shot (run_after) and repeating (run_every) -- is automatically cancelled.
  3. All zone watches are cleared.

You never need to manually clean up scheduled tasks on boss removal. However, you should still cancel repeating tasks when they are no longer needed during normal gameplay to avoid unnecessary work:

return {
api_version = 1,

on_enter_combat = function(context)
local pulse_count = 0
local task_id
task_id = context.scheduler:run_every(20, function(tick_context)
pulse_count = pulse_count + 1
if pulse_count > 10 or not tick_context.boss.exists then
tick_context.scheduler:cancel_task(task_id)
return
end
tick_context.world:spawn_particle_at_location(
tick_context.boss:get_location(),
{ particle = "FLAME", amount = 20, speed = 0.1 }
)
end)
end
}

Comportamiento del reloj por tick

The internal tick clock for a Lua power instance only runs when there is a reason to tick. Specifically, the clock is registered when either:

  • The power defines an on_game_tick hook, or
  • The power has created at least one zone watch via context.zones:watch_zone(...).

If neither condition is true, no per-tick overhead is incurred. The clock is unregistered automatically when the boss despawns or the runtime shuts down.


Comportamiento de errores y rendimiento

EliteMobs enforces strict error and performance limits on Lua powers:

Excepciones

If a hook function or a scheduled callback throws a Lua error (or a Java exception surfaces from an API call), the power is immediately disabled for that boss instance. The runtime is shut down and all owned tasks are cancelled.

The error is logged to the server console along with the filename of the power:

[EliteMobs] Lua power frost_cone.lua failed while handling on_boss_damaged_by_player.

Presupuesto de ejecución

Every hook invocation and every callback invocation is timed. If a single call takes longer than 50 milliseconds, the power is disabled with a console warning:

[EliteMobs] Lua power my_power.lua exceeded the 50ms execution budget on on_game_tick and was disabled.

This prevents runaway scripts from freezing the server. To stay within budget:

  • Avoid unbounded loops inside hooks. Use context.scheduler:run_every(...) to spread work across ticks.
  • Keep on_game_tick handlers lightweight -- they run every single tick.
  • Move heavy initialization into on_spawn or on_enter_combat rather than repeating it every tick.

Sandbox de Lua

Lua powers run inside a sandboxed LuaJ environment. Several globals that could access the filesystem or the Java runtime are removed.

Globales eliminados

The following standard Lua globals are set to nil and cannot be used:

RemovedWhy
debugExposes internal VM state
dofileFilesystem access
ioFilesystem access
loadArbitrary code loading
loadfileFilesystem access
luajavaDirect Java class access
moduleModule system (not needed)
osOperating system access
packageModule system (not needed)
requireModule system / filesystem access

Biblioteca estándar disponible

Everything else from the Lua standard library works normally:

CategoryFunctions
Mathmath.abs, math.ceil, math.floor, math.max, math.min, math.random, math.sin, math.cos, math.sqrt, math.pi, and all other math.* functions
Stringstring.byte, string.char, string.find, string.format, string.gsub, string.len, string.lower, string.match, string.rep, string.sub, string.upper, and all other string.* functions
Tabletable.insert, table.remove, table.sort, table.concat, and all other table.* functions
Iteratorspairs, ipairs, next
Typetype, tostring, tonumber, select, unpack
Error handlingpcall, xpcall, error, assert
Otherprint, rawget, rawset, rawequal, rawlen, setmetatable, getmetatable
tip

print writes to the server console, but prefer context.log:info(msg) or context.log:warn(msg) for output. These are prefixed with the power name, making it easier to trace which power produced the message.


Namespace auxiliar em

The em table is available at file load time (before any hook runs). It provides helper constructors for building location tables, vector tables, and zone definitions used throughout the API.

FunctionPurpose
em.create_location(x, y, z [, world, yaw, pitch])Create a location table with optional world name, yaw, and pitch
em.create_vector(x, y, z)Create a vector table
em.zone.create_sphere_zone(radius)Create a sphere zone definition
em.zone.create_dome_zone(radius)Create a dome zone definition
em.zone.create_cylinder_zone(radius, height)Create a cylinder zone definition
em.zone.create_cuboid_zone(x, y, z)Create a cuboid zone definition
em.zone.create_cone_zone(length, radius)Create a cone zone definition
em.zone.create_static_ray_zone(length, thickness)Create a static ray zone definition
em.zone.create_rotating_ray_zone(length, point_radius, animation_duration)Create a rotating ray zone definition
em.zone.create_translating_ray_zone(length, point_radius, animation_duration)Create a translating ray zone definition

Zone builders return chainable tables with :set_center(loc) (or :set_origin(loc) / :set_destination(loc) depending on zone type). These are designed to be used at the top of a file or inside hooks:

-- At file scope: create a reusable zone shape
local blast_zone = em.zone.create_sphere_zone(5)

return {
api_version = 1,

on_boss_damaged_by_player = function(context)
-- Anchor the zone to the boss's current location at call time
blast_zone:set_center(context.boss:get_location())

local entities = context.zones:get_entities_in_zone(blast_zone)
for i = 1, #entities do
if entities[i].type == "PLAYER" then
entities[i]:apply_potion_effect("SLOWNESS", 60, 1)
end
end
end
}

For a full breakdown of zone shapes, filters, watchers, and targeting patterns, see Zones & Targeting.


Próximos pasos