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 (runtime) 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.

Hooks de poderes de jefe

Esta página documenta los hooks para los poderes Lua de jefe en plugins/EliteMobs/powers/. Los scripts Lua de NPC usan su propia carpeta plugins/EliteMobs/npc_scripts/ y hooks específicos de NPC como on_npc_interact y on_npc_proximity_enter; consulta Scripts de NPC.


Referencia de hooks

Cada archivo de poder Lua devuelve una tabla. Cada clave de esa tabla (aparte de api_version y priority) debe ser uno de los hooks listados a continuación. El runtime llama a la función correspondiente cada vez que se activa el evento de juego asociado.

HookSe activa cuando¿context.player disponible?
on_spawnLa elite mob apareceNo
on_game_tickUna vez por cada tick del servidor (50 ms) mientras el reloj del runtime está activoNo
on_boss_damagedEl jefe recibe daño de cualquier fuenteNo
on_boss_damaged_by_playerEl jefe recibe daño de un jugador
on_boss_damaged_by_eliteEl jefe recibe daño de otra elite mobNo
on_player_damaged_by_bossUn jugador recibe daño de este jefe
on_enter_combatEl jefe entra en combate
on_exit_combatEl jefe sale del combateNo
on_healEl jefe se curaNo
on_boss_target_changedEl jefe cambia su objetivo
on_deathEl jefe muereNo
on_phase_switchUn jefe por fases cambia a una nueva faseNo
on_zone_enterUna entidad entra en una zona vigiladaSí (si la entidad es un jugador)
on_zone_leaveUna entidad sale de una zona vigiladaSí (si la entidad es un jugador)

Cuando context.player aparece como "No", acceder a él devuelve nil. Comprueba siempre que no sea nil antes de usarlo.

Origen del hook de zona

Los hooks de nivel superior on_zone_enter y on_zone_leave son activados por eventos de EliteScript/ScriptZone. Los vigilantes (watchers) creados en Lua desde context.zones:watch_zone(...) y context.script:zone(...):watch(...) llaman directamente a sus callbacks on_enter / on_leave en lugar de invocar estos hooks de nivel superior.

Poder típico con múltiples hooks

Un solo poder Lua puede definir tantos hooks como necesite. A continuación se muestra un esqueleto que usa tres hooks juntos:

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 de juego que activó el hook. Los campos disponibles dependen de qué hook se está ejecutando.

Hooks de daño

Se aplica a on_boss_damaged, on_boss_damaged_by_player, on_boss_damaged_by_elite y on_player_damaged_by_boss.

Campo / MétodoTipoDescripción
event.damage_amountdoubleValor de daño en bruto
event.damage_causestringNombre de DamageCause de Spigot (p. ej. "ENTITY_ATTACK", "PROJECTILE")
event.damagerentity tableEntidad que infligió el daño. Solo presente en los hooks de daño por entidad.
event.projectileentity tableLa entidad proyectil, si el atacante fue un proyectil.
event.set_damage_amount(n)Sobrescribe el daño con un valor fijo
event.multiply_damage_amount(n)Multiplica el daño actual por n
event.cancel_event()Cancela por completo el evento de daño
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 aparición

Se aplica a on_spawn.

Campo / MétodoTipoDescripción
event.spawn_reasonstringNombre de SpawnReason de Spigot
event.cancel_event()Cancela la aparición

Hook de muerte

Se aplica a on_death.

Campo / MétodoTipoDescripción
event.entityentity tableLa entidad que está muriendo

Hooks de zona

Se aplica a on_zone_enter y on_zone_leave.

Campo / MétodoTipoDescripción
event.entityentity tableLa entidad que entra o sale de la zona

Los vigilantes de zona creados en Lua no rellenan context.event; pasan la entidad que entra/sale directamente a su callback.

Eventos cancelables (general)

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

Para conocer todos los campos de la tabla de entidad, consulta Jefe y Entidades. Para los valores de causa de daño y razón de aparición, consulta Enums y Valores.


Orden de ejecución de los hooks

Cuando un jefe tiene varios poderes Lua adjuntos, el hook de cada poder se llama para el mismo evento. El orden se determina por el campo priority:

  • Los valores más bajos se ejecutan primero (el predeterminado es 0).
  • Los poderes con la misma prioridad se ejecutan en orden de carga (efectivamente no especificado).
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
}

La prioridad solo afecta al orden entre los poderes Lua del mismo jefe. No interactúa con el orden de ejecución de EliteScript.


Modelo de runtime

Un runtime por jefe

Cada entidad jefe obtiene su propia instancia de runtime Lua independiente. Cuando el jefe aparece, EliteMobs carga el código fuente Lua, lo evalúa en un entorno sandbox nuevo y almacena la tabla devuelta. Cuando el jefe desaparece o es eliminado, el runtime se cierra.

Esto significa:

  • Las variables globales de Lua establecidas durante la evaluación del archivo (como funciones auxiliares con local function) son privadas de ese jefe.
  • Las funciones de hook de la tabla devuelta nunca se comparten entre jefes.

Aislamiento de estado

Cada runtime tiene su propia tabla context.state. El estado de un jefe es completamente invisible para todos los demás jefes, incluso si comparten el mismo archivo de poder Lua. Usa context.state para almacenar contadores, banderas, temporizadores o cualquier dato por jefe que necesites a través de los 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 las tareas programadas

Todas las tareas creadas a través de context.scheduler son propiedad del runtime que las creó. Cuando un jefe desaparece:

  1. El runtime llama a shutdown().
  2. Cada tarea de su propiedad -- tanto las de un solo disparo (run_after) como las repetitivas (run_every) -- se cancela automáticamente.
  3. Todas las vigilancias de zona se limpian.

Nunca necesitas limpiar manualmente las tareas programadas al eliminar un jefe. Sin embargo, deberías cancelar las tareas repetitivas cuando ya no sean necesarias durante el juego normal para evitar trabajo innecesario:

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

El reloj de ticks interno de una instancia de poder Lua solo se ejecuta cuando el poder define un hook on_game_tick. Las vigilancias de zona creadas a través de context.zones:watch_zone(...) o context.script:zone(...):watch(...) crean sus propias tareas repetitivas propias en lugar de habilitar el hook de nivel superior on_game_tick del poder.

Si un poder no tiene ni on_game_tick ni vigilantes de zona, no se incurre en ningún trabajo por tick. El trabajo de ticks y las tareas de los vigilantes de zona se cancelan automáticamente cuando el jefe desaparece o el runtime se cierra.


Comportamiento de errores y rendimiento

EliteMobs impone límites estrictos de error y rendimiento a los poderes Lua:

Excepciones

Si una función de hook o un callback programado lanza un error de Lua (o una excepción de Java surge de una llamada a la API), el poder se desactiva inmediatamente para esa instancia de jefe. El runtime se cierra y todas las tareas de su propiedad se cancelan.

El error se registra en la consola del servidor junto con el nombre del archivo del poder, el número de línea y el hook que se estaba ejecutando:

[Lua] Error in 'frost_cone.lua' at line 35 during 'on_boss_damaged_by_player':
[Lua] -> ...explanation of what went wrong...
[Lua] -> Script has been disabled for this entity to prevent further errors.

Presupuesto de ejecución

Cada invocación de hook y cada invocación de callback se cronometra. Si una sola llamada tarda más de 50 milisegundos, el poder se desactiva con una advertencia en la consola:

[Lua] my_power.lua took 73ms in 'on_game_tick' (limit: 50ms) — script disabled to prevent lag.

Esto evita que los scripts descontrolados congelen el servidor. Para mantenerte dentro del presupuesto:

  • Evita los bucles sin límite dentro de los hooks. Usa context.scheduler:run_every(...) para repartir el trabajo entre los ticks.
  • Mantén los manejadores de on_game_tick ligeros -- se ejecutan en cada tick.
  • Mueve la inicialización pesada a on_spawn o on_enter_combat en lugar de repetirla en cada tick.

Sandbox de Lua

Los poderes Lua se ejecutan dentro de un entorno LuaJ con sandbox. Se eliminan varios globales que podrían acceder al sistema de archivos o al runtime de Java.

Globales eliminados

Los siguientes globales estándar de Lua están establecidos en nil y no se pueden usar:

EliminadoPor qué
debugExpone el estado interno de la VM
dofileAcceso al sistema de archivos
ioAcceso al sistema de archivos
loadCarga de código arbitrario
loadfileAcceso al sistema de archivos
luajavaAcceso directo a clases de Java
moduleSistema de módulos (no necesario)
osAcceso al sistema operativo
packageSistema de módulos (no necesario)
requireSistema de módulos / acceso al sistema de archivos

Biblioteca estándar disponible

Todo lo demás de la biblioteca estándar de Lua funciona normalmente:

CategoríaFunciones
Mathmath.abs, math.ceil, math.floor, math.max, math.min, math.random, math.sin, math.cos, math.sqrt, math.pi, y todas las demás funciones math.*
Stringstring.byte, string.char, string.find, string.format, string.gsub, string.len, string.lower, string.match, string.rep, string.sub, string.upper, y todas las demás funciones string.*
Tabletable.insert, table.remove, table.sort, table.concat, y todas las demás funciones table.*
Iteradorespairs, ipairs, next
Tipotype, tostring, tonumber, select, unpack
Manejo de errorespcall, xpcall, error, assert
Otrosprint, rawget, rawset, rawequal, rawlen, setmetatable, getmetatable
consejo

print escribe en la consola del servidor, pero prefiere context.log:info(msg) o context.log:warn(msg) para la salida. Estos llevan como prefijo el nombre del poder, lo que facilita rastrear qué poder produjo el mensaje.


Espacio de nombres auxiliar em

La tabla em está disponible en el momento de carga del archivo (antes de que se ejecute cualquier hook). Proporciona constructores auxiliares para construir tablas de ubicación, tablas de vector y definiciones de zona usadas en toda la API.

FunciónPropósito
em.create_location(x, y, z [, world, yaw, pitch])Crea una tabla de ubicación con nombre de mundo, yaw y pitch opcionales
em.create_vector(x, y, z)Crea una tabla de vector
em.zone.create_sphere_zone(radius)Crea una definición de zona esférica
em.zone.create_dome_zone(radius)Crea una definición de zona de cúpula
em.zone.create_cylinder_zone(radius, height)Crea una definición de zona cilíndrica
em.zone.create_cuboid_zone(x, y, z)Crea una definición de zona cuboide
em.zone.create_cone_zone(length, radius)Crea una definición de zona cónica
em.zone.create_static_ray_zone(length, thickness)Crea una definición de zona de rayo estático
em.zone.create_rotating_ray_zone(length, point_radius, animation_duration)Crea una definición de zona de rayo rotatorio
em.zone.create_translating_ray_zone(length, point_radius, animation_duration)Crea una definición de zona de rayo trasladante

Los constructores de zona devuelven tablas encadenables con :set_center(loc) (o :set_origin(loc) / :set_destination(loc) dependiendo del tipo de zona). Están diseñados para usarse en la parte superior de un archivo o dentro de los 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
}

Para un desglose completo de las formas de zona, filtros, vigilantes y patrones de selección de objetivos, consulta Zonas y Selección de Objetivos.


Próximos pasos