Scripting Lua: Primeros Pasos
Lo que aprenderás
Esta página te enseña a escribir tu primer poder Lua para EliteMobs, desde un archivo vacío hasta una habilidad de jefe funcional. Al final comprenderás los hooks, el context, los tiempos de reutilización y la estructura general de cada archivo de poder Lua.
Una vez que te sientas cómodo con los conceptos básicos, continúa con las páginas complementarias:
- Hooks y Ciclo de Vida -- nombres de hooks, orden de ejecución y disponibilidad del context
- Scripts de NPC -- ciclo de vida, interacción y hooks de proximidad de NPC
- Jefe y Entidades -- las APIs
context.boss,context.player,context.playersycontext.entities - Mundo y Entorno -- partículas, sonidos, rayos, aparición y la API
context.world - Zonas y Objetivos -- zonas nativas de Lua, utilidades de Script para objetivos y las APIs
context.zones/context.script - Ejemplos y Patrones -- poderes completos y funcionales que puedes estudiar y adaptar
- Enums y Valores -- enlaces a los Javadocs de Spigot para Particle, Material, PotionEffectType y otras constantes de cadena
- Solución de Problemas -- errores comunes, consejos de depuración y la lista de verificación QC
- Programas de Comportamiento -- IA de mobs escrita en Lua que se selecciona con el ajuste
behaviorde un jefe
La mayoría de los poderes incluidos en EliteMobs usan Lua, pero los nombres y comportamientos de los métodos auxiliares pueden cambiar entre versiones. Comprueba los poderes personalizados en un servidor de pruebas antes de usarlos en producción y revisa la referencia después de actualizar.
Lua no reemplaza el sistema YAML eliteScript: existente, y EliteScript no está obsoleto.
- Usa EliteScript cuando quieras scripting declarativo basado en YAML con las páginas existentes de Acciones, Objetivos, Zonas, Condiciones, Tiempos de Reutilización y Vectores Relativos.
- Usa archivos de poder Lua cuando necesites variables, bucles, selección aleatoria, funciones auxiliares reutilizables, estado persistente por jefe y un flujo de programación más tradicional.
La API de utilidades de script (context.script) pasa tus tablas a las clases de objetivos, zonas, vectores relativos y partículas de EliteScript. Comparte sus analizadores y limitaciones, pero los handles Lua independientes no proporcionan todos los contextos de las acciones YAML. Los objetivos heredados y las ubicaciones de aterrizaje necesitan un contexto ausente en esos handles; los auxiliares Lua tampoco aplican automáticamente esperas, condiciones o caché de objetivos de acciones YAML. La página de zonas y objetivos explica estas diferencias.
Qué son los poderes Lua
Los poderes Lua son archivos .lua independientes que se encuentran en el árbol normal de powers de EliteMobs y se referencian exactamente como los archivos de poder normales.
Esta página trata los poderes Lua de jefes. Los scripts Lua de NPC son una función distinta: se guardan en plugins/EliteMobs/npc_scripts/, se asignan mediante la lista scripts: del NPC y usan hooks específicos como on_npc_proximity_enter. Consulta Scripts de NPC.
Para qué son buenos los poderes Lua
Los poderes Lua brillan cuando necesitas:
- Rotaciones de ataque y selección aleatoria de movimientos
- Estado persistente entre hooks con
context.state - Acciones retrasadas y repetidas sin construir todo con esperas YAML
- Funciones auxiliares personalizadas compartidas dentro de un archivo
- Ramificaciones complejas que serían incómodas en EliteScript puro
- Lógica de jefe que aún quiere reutilizar definiciones de objetivos y zonas estilo EliteScript
Si tu poder es principalmente "activar evento, ejecutar unas pocas acciones con script", los documentos existentes de EliteScript siguen siendo la forma más rápida y clara de construirlo. Si tu poder necesita flujo de programa real, Lua es la herramienta para ese trabajo.
Para quién es esta página
Esta página está escrita para tres tipos de lectores:
- Alguien que ya conoce EliteScript y quiere aprender Lua sin aprender "programación real" de golpe
- Alguien que es nuevo en el scripting de EliteMobs y necesita una referencia completa con nombres exactos
- Alguien que usa IA para redactar poderes y necesita suficiente detalle para saber cuándo la IA inventó algo falso
No necesitas convertirte en un desarrollador Lua completo antes de escribir poderes útiles. Para la mayoría de los poderes prácticos de EliteMobs, lo único que realmente necesitas es:
- Cómo colocar un hook válido en la tabla devuelta
- Cómo leer valores de
context - Cómo detenerte temprano con
if ... then return end - Cómo llamar a algunos métodos auxiliares correctamente
- Cómo copiar las especificaciones correctas de objetivos y zonas de los documentos existentes de EliteScript
Modelo Mental: EliteScript vs. Lua
Si conoces EliteScript, esta comparación es la forma más rápida de entender los poderes Lua:
| Si piensas en términos de EliteScript | En Lua eso normalmente significa |
|---|---|
| Events | Nombres de hooks como on_spawn o on_boss_damaged_by_player |
| Cooldowns | context.cooldowns (ver context.cooldowns más abajo) |
| Acciones | Llamadas directas a métodos como context.world:spawn_particle_at_location(...) o context.script:damage(...) |
| Objetivos | context.script:target({...}) -- usa los nombres de campo de EliteScript Target |
| Zonas | context.script:zone({...}) o ayudantes nativos context.zones -- ver Zonas y Objetivos |
| Vectores relativos | context.script:relative_vector({...}) o context.vectors |
| Flujo del script | Tus propias sentencias if de Lua, funciones auxiliares, temporizadores y estado |
La mayor diferencia es esta:
- EliteScript dice qué debería pasar en YAML.
- Lua te permite decidir cuándo, por qué y qué rama debería ejecutarse usando código.
Así que si EliteScript se siente como "configuración", Lua se siente como "configuración más toma de decisiones".
Mini introducción a Lua
La mayoría de los poderes usan unos pocos conceptos Lua: variables (local x = 5), funciones (function foo() end), condiciones if (if x then ... end), tablas ({key = value}) y nil (sin valor). Los puntos y coma son opcionales. Las tablas usan llaves; las funciones y los bloques de control se cierran con end.
Para un recorrido completo con ejemplos, consulta el Motor de Scripting Lua de MagmaCore -- Mini introducción a Lua. Esa introducción se comparte entre todos los plugins de Nightbreak.
Tu primer poder funcional, construido paso a paso
Si eres completamente nuevo, esta es la mejor progresión para la "primera victoria".
Antes del Paso 1: Guárdalo y adjúntalo a un jefe
Guarda el archivo Lua en la carpeta normal de poderes de EliteMobs, por ejemplo:
plugins/
EliteMobs/
powers/
first_test.lua
Luego añade ese nombre de archivo a la configuración del jefe usando la lista normal powers::
powers:
- first_test.lua
Así que el bucle completo para principiantes es:
- Guardar el archivo en
plugins/EliteMobs/powers/ - Añadir el nombre de archivo
.luaa la listapowers:del jefe - Ejecutar
/em reloadpara cargar los archivos modificados y después invocar un jefe de prueba nuevo. La recarga afecta al plugin y a su contenido activo, así que usa un servidor de pruebas. - Activar el hook y comprobar su resultado visible y la consola. Guardar el archivo o invocar otro jefe sin recargar no actualiza una definición ya cargada.
Si necesitas más contexto sobre archivos de jefe, listas de poderes o cómo están estructurados los jefes personalizados, consulta Crear Jefes Personalizados. Esa página cubre la configuración de jefes en detalle, incluyendo cómo funciona la lista powers:.
Paso 1: Hacer que el archivo cargue
return {
api_version = 1,
on_spawn = function(context)
end
}
El cargador valida la sintaxis Lua, la tabla devuelta y los nombres de hooks. Un hook vacío no produce ningún resultado visible, por lo que el silencio no demuestra que se haya encontrado o asignado el archivo. Añade el sonido del paso 2 e invoca un jefe nuevo para comprobarlo.
Paso 2: Hacer que el jefe haga algo visible
return {
api_version = 1,
on_spawn = function(context)
context.boss:play_sound_at_self("entity.blaze.ambient", 1.0, 1.0)
end
}
Ahora tienes la prueba más importante para principiantes: tu hook se está ejecutando.
Paso 3: Reaccionar al golpe de un jugador
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
}
Esto enseña tres ideas fundamentales:
on_boss_damaged_by_playeres el nombre del hookcontext.playeres el jugador involucrado en ese hookreturnsale temprano cuando faltan los datos que necesitas
Paso 4: Prevenir spam con un tiempo de reutilización
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
}
Cada tick del cooldown local representa 50 milisegundos de tiempo transcurrido: 60 son tres segundos incluso si bajan los TPS del servidor. Esta clave de cooldown se comparte entre los poderes de este jefe, no entre jefes distintos. Al combinar poderes personalizados, añade el nombre del poder como prefijo de las claves. Este ejemplo solo comprueba la clave local, no el cooldown global del jefe.
Paso 5: Añadir un efecto real
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.cooldowns:set_local(100, "shock")
context.player:send_message("&cStatic jumps from the boss into your armor!")
context.world:strike_lightning_at_location(context.player.current_location)
end
}
Esto crea un rayo real en el mundo, no solo un destello visual. El cooldown se establece antes del efecto para que los eventos que provoque lo encuentren activo. Este cooldown local de 100 ticks equivale a cinco segundos de tiempo transcurrido.
Primer flujo de trabajo real
Al construir un nuevo poder Lua desde cero, usa este orden:
- Crear el archivo y hacer que
on_spawnfuncione. - Cambiar al hook que realmente quieres.
- Confirmar que el hook tiene los datos que esperas, como
context.playerocontext.event. - Añadir primero un mensaje o sonido, antes del daño o las partículas.
- Añadir tiempos de reutilización.
- Añadir un efecto de juego.
- Solo después de eso, añadir auxiliares, estado, lógica de planificador o zonas.
Ese orden hace que la depuración sea dramáticamente más fácil porque solo cambia una cosa a la vez.
Dónde van los archivos Lua
Coloca los archivos .lua en el mismo árbol de carpetas que los archivos de poder .yml normales:
plugins/
EliteMobs/
powers/
mycoolpower.lua
attack_push.yml
subfolder/
myotherpower.lua
Los poderes Lua se descubren automáticamente desde los directorios de poderes que EliteMobs ya carga.
Metadatos YAML opcionales
Un mycoolpower.lua personalizado puede tener un mycoolpower.yml o mycoolpower.yaml cargado en el mismo directorio. El descubrimiento lee isEnabled, effect, powerType, powerCooldown y globalCooldown de ese YAML; isEnabled: false impide registrar ese Lua personalizado. Sin metadatos coincidentes no tiene efecto y usa el tipo MISCELLANEOUS. Los hooks Lua siguen controlando los ataques y deben comprobar sus propios cooldowns.
Un YAML cargado con el mismo nombre base junto a un poder Lua incluido aplica esos mismos campos a ese poder. Sin él, el poder incluido conserva sus metadatos de fábrica. Consulta Crear Poderes para los campos de metadatos.
Cómo los jefes referencian poderes Lua
Los archivos de jefe siguen usando la lista normal powers::
powers:
- attack_push.yml
- mycoolpower.lua
No se requiere ningún campo especial. Los poderes Lua no se cargan a través de eliteScript:.
Reglas de nomenclatura de archivos
- Los jefes referencian poderes Lua por nombre de archivo, no por ruta de carpeta.
- EliteMobs actualmente registra los poderes Lua descubiertos solo por nombre base.
- Evita nombres duplicados como
powers/fire.luaypowers/bosses/fire.lua. EliteMobs solo carga el archivo cuya ruta completa va primero en orden alfabético y registra una advertencia sobre la copia omitida, salvo que ambos archivos sean idénticos.
Poderes Lua prefabricados
La mayoría de los poderes incluidos usan Lua. El código actual define 68 poderes Lua prefabricados concretos, instalados en plugins/EliteMobs/powers/ cuando faltan, como attack_fire.lua, frost_cone.lua, meteor_shower.lua y shield_wall.lua. Lee esos archivos como ejemplos, teniendo en cuenta que una copia local modificada puede diferir de la incluida en el plugin.
Los archivos prefabricados solo se escriben si faltan, por lo que los cambios sobreviven a reinicios. Cuando una actualización cambia un poder incluido, al arrancar se mueve a plugins/MagmaCore/outdated files/EliteMobs/ cualquier copia sin editar de una versión incluida anteriormente y se escribe la versión actual en su lugar; las copias editadas se conservan. Eliminar uno permite regenerar la versión incluida en la siguiente inicialización del plugin; conserva antes los cambios que aún necesites.
Fragmentos de código compartidos
Los poderes Lua de jefe pueden incluir un archivo auxiliar mediante una directiva en una línea de comentario independiente:
-- @include helpers/common.inc
El cargador inserta el texto del archivo antes de validar el poder. Las rutas son relativas al archivo que contiene la directiva y deben permanecer dentro del directorio del archivo de poder principal, incluso al resolver enlaces simbólicos. Se rechazan las inclusiones circulares, el anidamiento de más de 16 niveles, los archivos mayores de 4 MiB y el código expandido de más de 4.194.304 caracteres.
Usa una extensión distinta de .lua, como .inc, para que el descubrimiento no intente cargar los fragmentos como poderes independientes. Declara las funciones auxiliares antes de la tabla return final. Esta directiva solo pertenece al cargador de poderes de jefe: no activa require ni el acceso al sistema de archivos, y los scripts de NPC no la procesan.
Los nombres .yml heredados siguen resolviéndose
Las referencias a attack_fire.yml siguen funcionando. Para una referencia .yml o .yaml, EliteMobs busca primero un poder Lua cargado con el mismo nombre base y después el nombre literal. Un archivo desactivado o que no haya superado la validación no es un reemplazo cargado. Mantén la escritura y mayúsculas del nombre base. Para personalizar un poder Lua incluido, edita su archivo existente y recarga; duplicar nombres base en otros directorios no es una sustitución fiable.
Contrato mínimo del archivo
Cada poder Lua debe devolver una tabla con return. Esa tabla es intencionalmente estricta.
Campos de nivel superior requeridos y opcionales
| Campo | Requerido | Tipo | Notas |
|---|---|---|---|
api_version | Sí | Número | Usa el entero 1 |
priority | No | Número | Usa un entero. Los valores inferiores se ejecutan antes; predeterminado 0. Los poderes YAML usan prioridad 0 |
| Claves de hook soportadas | No | Función | Debe usar un nombre exacto de la Referencia de Hooks |
Reglas de validación
- El archivo debe devolver una tabla.
api_versiones requerido y actualmente debe ser1.prioritydebe ser numérico si está presente.- Cada clave de nivel superior adicional debe ser un nombre de hook soportado.
- Cada clave de hook debe apuntar a una función.
- Las claves de nivel superior desconocidas son rechazadas.
Esto significa que las funciones auxiliares y las constantes locales deben ubicarse encima del return final, no dentro de la tabla devuelta a menos que sean hooks reales.
El cuerpo del archivo se evalúa al validar y de nuevo en un entorno Lua separado para cada ejecución del poder en un jefe. context solo existe como argumento de un hook o callback, no como global de nivel superior. Las variables locales pueden persistir durante esa ejecución, pero los valores de validación no se transfieren. El entorno elimina el acceso a archivos, sistema operativo y Java, además de require; usa la directiva include para fragmentos. Un error no capturado en un hook o callback cierra esa ejecución y cancela sus tareas. No vuelve a intentarse en el siguiente golpe. Corrige el archivo y recarga antes de repetir.
Plantillas para copiar y pegar
Poder Lua válido más pequeño
return {
api_version = 1,
on_spawn = function(context)
end
}
Plantilla inicial recomendada
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
}
Diseño para archivos más grandes
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
Los hooks son funciones con nombres especiales en la tabla que devuelves. EliteMobs los llama cuando algo sucede — el jefe aparece, recibe daño, entra en combate, etc. Ya viste on_spawn y on_boss_damaged_by_player en el tutorial anterior.
Los hooks más comunes para empezar son on_spawn, on_boss_damaged_by_player, on_enter_combat y on_exit_combat. Para la lista completa de todos los hooks, qué claves de context están disponibles en cada uno y cómo funciona el orden de ejecución, consulta Hooks y Ciclo de Vida.
¿Qué es context?
Cada función de hook recibe un argumento llamado context. Piensa en él como una caja de herramientas que EliteMobs te entrega cada vez que algo sucede — contiene todo lo que necesitas para interactuar con el jefe, el jugador, el mundo, los tiempos de reutilización y más.
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
Tú no creas context — EliteMobs lo crea y lo pasa a tu hook. Simplemente lees de él y llamas métodos en él.
Cada hook recibe un context nuevo. Su tabla context.state pertenece a la ejecución de este poder en este jefe y puede almacenar datos para hooks y callbacks posteriores. Otros poderes tienen su propio estado; cerrar o recargar la ejecución lo descarta.
APIs clave de context
Aquí están las APIs más importantes que usarás de context:
-
context.state— Una tabla Lua conservada durante la ejecución de este poder en este jefe. Guarda fases, IDs de tareas y banderas entre hooks. No se conserva al eliminar o recargar la ejecución. -
context.log— Registro en consola conlog:info(msg),log:warn(msg)ylog:debug(msg). Incluye el nombre del poder en el mensaje si necesitas distinguir la salida de varios poderes. -
context.cooldowns— Cooldowns con nombre y un cooldown global compartidos por los poderes del jefe. Las claves explícitas usan un espacio común por jefe; añade el nombre del poder como prefijo. Una clavenilelige el cooldown predeterminado de este archivo Lua.cooldowns:check_local(key, ticks)comprueba y establece el cooldown local en una llamada; no comprueba ni establece el global. Consulta Hooks y Ciclo de Vida. -
context.scheduler—scheduler:run_after(ticks, callback)retrasa una llamada.scheduler:run_every(ticks, callback)repite sin esperar el primer intervalo; usascheduler:run_repeating(delay, interval, callback)para fijar la espera inicial. Los callbacks reciben un context nuevo sin el evento ni el jugador que los originó. Usa ese argumento y guarda deliberadamente el UUID del jugador o la posición necesarios; no supongas quectx.playerse conserva tras la espera. Cancela las repeticiones exclusivas de combate enon_exit_combat; de lo contrario continúan hasta cancelarlas o cerrar la ejecución. Consulta Hooks y Ciclo de Vida. -
context.boss/context.player— El jefe y el jugador involucrados en el evento actual. Consulta Jefe y Entidades para todos los campos y métodos. -
context.world— Generar partículas, entidades, sonidos, rayos, bloques. Consulta Mundo y Entorno. -
context.zones/context.script— Geometría de zonas, objetivos, daño, partículas. Consulta Zonas y Objetivos.
Sintaxis de métodos: : vs. .
Para una explicación de : y ., consulta el Motor de Scripting Lua de MagmaCore. Los métodos de entidades y servicios compartidos aceptan ambas formas. Los setters context.event.set_damage_amount(value) y context.event.multiply_damage_amount(multiplier) requieren punto; los dos puntos pasan la tabla del evento como primer argumento. context.event:transfer_damage(target, amount) sí acepta dos puntos. Los constructores globales como em.create_location(...) usan punto.
Próximos pasos
Ahora que conoces los conceptos básicos, explora el resto de la documentación de scripting Lua:
- Hooks y Ciclo de Vida -- nombres de hooks, orden de ejecución y qué claves de context están disponibles en cada hook
- Scripts de NPC -- ubicación de archivos, hooks, proximidad y
context.npc - Jefe y Entidades --
context.boss,context.player,context.players,context.entitiesycontext.event - Mundo y Entorno -- partículas, sonidos, rayos, aparición y
context.world - Zonas y Objetivos -- zonas nativas de Lua, utilidades de Script para objetivos/zonas/partículas y vectores relativos
- Ejemplos y Patrones -- poderes completos y funcionales con explicaciones
- Enums y Valores -- enlaces a los Javadocs de Spigot para Particle, Material, PotionEffectType y más
- Solución de Problemas -- errores comunes, consejos de depuración y una lista de verificación QC
Para scripting basado en YAML, las páginas de EliteScript siguen siendo la referencia canónica:
