Scripting Lua: Solución de problemas
Esta página cubre los problemas comunes que puedes encontrar al escribir o depurar poderes en Lua, además de consejos de migración para autores que vienen de EliteScript. Si estás depurando scripts de NPC, consulta Scripts de NPC. Si buscas ejemplos funcionales, consulta Ejemplos y patrones. Si apenas estás comenzando, consulta Primeros pasos.
EliteMobs utiliza el motor de scripting Lua de MagmaCore, compartido entre los plugins de Nightbreak. Para la documentación sobre conceptos compartidos como el sandbox, el planificador, las zonas, la API del mundo, las tablas de entidades y los métodos de UI del jugador, consulta la página del Motor de scripting Lua de MagmaCore.
Problemas comunes
1. El poder no se carga en absoluto
Revisa la consola del servidor en busca de errores cuando el servidor arranca. La causa más común es un error de sintaxis en Lua (falta un end, paréntesis sin cerrar, etc.). Verifica también que el archivo termine en .lua y esté ubicado en el directorio powers correcto. Para nombres personalizados, comprueba si un archivo de metadatos .yml o .yaml del mismo directorio y con el mismo nombre base establece isEnabled: false; esto impide el descubrimiento. Un archivo de metadatos con el mismo nombre base junto a un poder incluido también aplica a ese poder sus campos isEnabled, effect, powerType y de tiempos de reutilización. Si dos archivos de poder comparten nombre en carpetas distintas, solo se carga uno; la consola indica la copia omitida.
El archivo debe devolver una tabla que contenga api_version = 1. Esa tabla solo admite api_version, el campo opcional priority y nombres de hooks compatibles; los valores de los hooks deben ser funciones. Mantén las funciones auxiliares como locales en vez de añadir claves arbitrarias a la tabla devuelta. El código de nivel superior se ejecuta al validar la definición y al inicializar el runtime, sin contexto de hook.
2. El hook nunca se dispara
Verifica que el nombre del hook esté escrito exactamente como aparece en la lista de hooks. Errores comunes: on_boss_hit (incorrecto) en lugar de on_boss_damaged_by_player (correcto), o on_tick (incorrecto) en lugar de on_game_tick (correcto).
Una clave no admitida en la tabla devuelta impide cargar toda la definición. Si se carga pero no se ejecuta, comprueba también que el jefe haga referencia al archivo del poder, que ocurra el evento correspondiente y que el runtime no haya fallado, esté pausado o tenga sus poderes suprimidos.
3. context.player es nil
Solo los hooks cuyo evento subyacente lleva un jugador rellenan context.player. Siempre es nil en on_spawn, on_game_tick, on_boss_damaged, on_boss_damaged_by_elite, on_exit_combat, on_heal, on_death, on_phase_switch y on_mind_action.
Se rellena en on_boss_damaged_by_player, on_reinforcement_damaged_by_player, on_player_damaged_by_boss, on_enter_combat y on_boss_target_changed, y en on_zone_enter / on_zone_leave cuando la entidad implicada resulta ser un jugador. Añade siempre una comprobación de nil antes de usarlo. Consulta Hooks y ciclo de vida para ver la tabla completa.
4. Tiempo de espera / presupuesto de ejecución excedido
Cada hook, callback programado y evaluación de archivo dispone de un presupuesto compartido con las llamadas anidadas: 250.000 instrucciones Lua y 50 ms de CPU del hilo. Si la JVM no puede medir ese tiempo de CPU, se usa un límite alternativo de 250 ms de tiempo transcurrido. El error indica el límite superado:
Lua instruction budget exceeded (250000 instruction limit)
Lua CPU-time budget exceeded (50ms current-thread CPU limit)
Lua elapsed-time fallback budget exceeded (250ms fallback; current-thread CPU time unavailable)
La VM interrumpe los bucles Lua descontrolados durante su ejecución. No puede interrumpir una llamada Java mientras se ejecuta, y no existe un corte adicional de 50 ms de tiempo real tras el retorno. Entre las causas habituales están recorrer demasiadas entidades, generar partículas en una zona grande sin coverage y realizar trabajo costoso cada tick. Usa cooldowns, menos ubicaciones de muestreo o context.scheduler:run_every(...) para repartir el trabajo entre ticks.
5. El callback del planificador usa datos obsoletos
Probablemente estás usando el context externo en lugar del parámetro del callback. Cambia function() ... context.boss ... end por function(tick_context) ... tick_context.boss ... end.
Los callbacks programados reciben un contexto nuevo sin el evento, objetivo directo ni jugador que los originaron. Captura explícitamente el objetivo necesario antes de programarlos y comprueba que siga siendo válido al usarlo. Vuelve a consultar las ubicaciones y otras propiedades cambiantes en vez de conservar una tabla anterior.
6. La consulta de zona no devuelve entidades
Revisa con cuidado la definición de la zona. Para las zonas nativas, asegúrate de que kind esté en minúsculas ("sphere", no "SPHERE"). Para las utilidades de script, asegúrate de que shape esté en mayúsculas ("CONE", no "cone"). Verifica también que origin o Target realmente resuelva a una ubicación válida.
Comprueba también el filtro de entidades y el mundo. Las consultas nativas de zonas del jefe usan living de forma predeterminada, excluyen al jefe y buscan en su mundo. Las zonas de las utilidades de script usan PLAYER por defecto. Obtén los objetivos de zona Lua con full_target() o border_target() del identificador de zona; context.script:target({targetType = "ZONE_FULL"}) por sí solo no tiene una zona asociada.
7. Las partículas no aparecen
Usa un nombre válido del enum Particle de Bukkit; "FLAME" y "flame" se normalizan a mayúsculas. Comprueba la ubicación, la cantidad y los datos requeridos. BLOCK necesita datos de bloque que este helper del mundo no proporciona: usa otra partícula compatible o la API de partículas de script con su opción de material.
8. El enfriamiento no parece funcionar
check_local(key, duration) comprueba y establece el enfriamiento en una llamada. También puedes usar local_ready(key) y después set_local(duration, key) cuando comience el ataque; comprobar la disponibilidad por sí solo nunca inicia el enfriamiento. Los poderes del jefe comparten las claves explícitas, así que usa un prefijo específico para cada poder. Los metadatos YAML no bloquean automáticamente la ejecución de hooks Lua.
9. El jefe sigue usando poderes después de morir
Las tareas de context.scheduler se cancelan cuando se cierra el runtime del poder. Cancela los bucles de ataque en on_exit_combat si deben detenerse antes de retirar al jefe. Revisa también la función auxiliar que llamaste: algunas funciones Java crean sus propias tareas. Por ejemplo, world:run_fake_gold_nugget_damage(...) ya ejecuta un bucle por tick con su propio límite de tiempo y debe llamarse una sola vez por lote de proyectiles.
Lectura de los mensajes de error
Cuando un poder Lua falla durante la ejecución, la consola muestra un bloque con el prefijo [Lua] que identifica el archivo y el hook o callback. Incluye el número de línea si puede extraerlo del error original. Lee el mensaje completo antes de depurar. En archivos con -- @include, las líneas corresponden al código expandido.
Un error típico se ve así:
[Lua] Error in 'push_zone.lua' at line 35 during 'on_boss_damaged_by_player':
[Lua] -> You tried to call a method or function that doesn't exist.
[Lua] -> Check the method name for typos, or make sure you're using ':' (colon) for method calls, not '.' (dot).
[Lua] -> Script has been disabled for this entity to prevent further errors.
El sistema traduce los errores comunes de Lua a lenguaje sencillo. Estos son los más frecuentes:
| Error crudo de Lua | Lo que te dice la consola |
|---|---|
attempt to call nil | Intentaste llamar a un método o función que no existe. Revisa el nombre del método en busca de erratas, o asegúrate de usar : (dos puntos) para las llamadas a métodos, no . (punto). |
index expected, got nil | Intentaste acceder a un campo de algo que es nil. Verifica que el código anterior lo haya inicializado. |
attempt to index | Intentaste acceder a una propiedad de un valor nil o inválido. |
bad argument | Muestra los detalles concretos del desajuste de argumentos (tipo esperado frente a tipo real). |
| Presupuesto de ejecución excedido | El mensaje identifica el límite de instrucciones, tiempo de CPU o tiempo transcurrido alternativo; consulta la sección de presupuesto anterior. |
La instancia del script que falla queda desactivada para esa entidad. Tras corregir el archivo, debes recargar su definición y crear un runtime nuevo; una instancia desactivada sin cambios no reintenta el hook que falla cada tick.
No asumas que existen alias no documentados
La API de Lua expone un conjunto específico de nombres de métodos. Si escribes poderes a mano o con la ayuda de una IA, no asumas que existen abreviaturas o nombres alternativos. Los siguientes son ejemplos de nombres que no existen y que provocarán errores:
show_temporary_boss_bar()-- usaplayer:show_boss_bar(title, color, style, duration)en su lugar.run_command_as_player()-- usaplayer:run_command(command)en su lugar.em.location(...)-- el nombre del método es incorrecto. Usaem.create_location(x, y, z)en su lugar, ocontext.boss:get_location()/context.player.current_location.em.vector(...)-- el nombre del método es incorrecto. Usaem.create_vector(x, y, z)en su lugar, o tablas simples{x=0, y=1, z=0}.em.zone.sphere(...)-- el nombre del método es incorrecto. Usaem.zone.create_sphere_zone(radius)en su lugar, o una tabla de definición de zona como{kind = "sphere", radius = 5, origin = location}.entity:teleport_to(...)-- usaentity:teleport_to_location(location).- En los wrappers de entidades específicos de jefes de EliteMobs, usa
entity:set_velocity_vector(vector). Los wrappers compartidos de MagmaCore también exponenset_velocity(...); comprueba qué wrapper devuelve tu contexto. entity:set_facing(...)-- usaentity:face_direction_or_location(direction_or_location).
En caso de duda, consulta las páginas de Referencia de la API (Jefes y entidades, Mundo y entorno, Zonas y objetivos, o la API compartida). Los contextos similares pueden exponer métodos distintos; un alias plausible no lo convierte en disponible.
Consejos de migración para autores de EliteScript
Si ya escribes buenos EliteScripts, la forma más fácil de aprender los poderes en Lua es:
-
Sigue pensando en términos de eventos, objetivos, zonas, vectores relativos y partículas. Los conceptos son los mismos: solo cambia la sintaxis. Los eventos de EliteScript se convierten en nombres de hooks como
on_spawnuon_boss_damaged_by_player. Los objetivos y las zonas se pasan como tablas acontext.scriptusando los mismos nombres de campos documentados en las páginas de Zonas de EliteScript y Objetivos de EliteScript. -
Lleva tu flujo de control a Lua. Las tiradas aleatorias, las funciones auxiliares compartidas, los bucles, el estado persistente (
context.state) y la planificación de tareas (context.scheduler) son las cosas que Lua añade y que el EliteScript puro no puede hacer fácilmente. Comienza convirtiendo a Lua un poder con ramificaciones o condicionales mientras mantienes todo lo demás igual. -
Usa
context.scriptpara la selección de objetivos y la geometría de zonas. Sus tablas de especificación utilizan los analizadores reales de EliteScript paratargetType,shape,Target,Target2,FinalTarget,range,offset,relativeOffset,coverageyfilter. Ten a mano la documentación de EliteScript para los nombres de campos y sus valores por defecto, pero lee también las limitaciones de los handles Lua. Los handles Lua estáticos no aplican el almacenamiento en cachétrack: falsede la acción YAML, y faltan los datos de acciones heredadas.offsetyrelativeOffsetse aplican a las ubicaciones resueltas, incluidasLOCATION,LOCATIONSy las ubicaciones de zona muestreadas. Usa un origen fijo explícito cuando el área deba permanecer en su sitio. -
Cuidado con los dos sistemas de zonas.
context.script:zone({shape = "SPHERE", ...})usa el motor de EliteScript con enums en mayúsculas y tablas de especificaciónTarget.context.zonesusakinden minúsculas y ubicacionesorigin/destinationsimples. Mezclar estos estilos de claves puede producir una zona vacía o inesperada, advertencias o un error de ejecución.
Ruta de progresión para principiantes
Si quieres aprender este sistema desde cero, esta progresión funciona bien:
- Escribe un archivo solo con
api_version = 1yon_spawn. - Haz que el jefe envíe un mensaje o reproduzca un sonido.
- Añade un enfriamiento con
context.cooldowns. - Añade un hook activado por el jugador, como
on_boss_damaged_by_player. - Añade una acción retardada con
context.scheduler:run_after(...). - Añade una consulta de zona nativa de Lua simple o un
context.script:target(...)simple. - Solo entonces pasa a ataques rotatorios, máquinas de estados y mecánicas de varios pasos.
Cada paso se apoya en el anterior, y puedes probar en cada etapa. No intentes escribir un jefe de varias fases como tu primer poder en Lua.
Próximos pasos
- Primeros pasos -- estructura de archivos, hooks, recorrido del primer poder, plantillas para copiar y pegar
- Scripts de NPC -- scripts de proximidad, interacción y ciclo de vida de los NPC
- Ejemplos y patrones -- poderes funcionales completos que puedes estudiar y adaptar
