Saltar al contenido principal

Scripts Lua: solución de problemas

Esta página cubre los problemas habituales al escribir o depurar scripts de elementos decorativos de FreeMinecraftModels, además de consejos sobre la generación diferida de configuración. Si buscas ejemplos funcionales, consulta Ejemplos y patrones. Si estás empezando, consulta Primeros pasos.

Motor Lua de MagmaCore

FMM usa el runtime ScriptInstance de MagmaCore. Para la documentación del sandbox, el planificador, las zonas, la API de mundo, las tablas de entidad y los métodos de UI de jugador de FMM/MagmaCore, consulta la página del Motor de Scripting Lua de MagmaCore. Los poderes de jefe y los scripts de NPC de EliteMobs reutilizan el sandbox de Lua pero exponen sus propias tablas de context.


Problemas habituales​

1. El archivo de configuración no se carga / El script no está asignado​

Síntoma: El prop aparece pero no responde a clics ni a ningún hook.

Causas y soluciones:

  • Aún no existe un archivo .yml. FMM intenta crearlo de forma asíncrona al generar el primer prop, con valores predeterminados (habilitado, sin scripts), sin sobrescribir archivos existentes. Añade los nombres de scripts, completa /fmm reload correctamente y coloca un prop nuevo. Tanto YAML como Lua requieren recarga tras una edición.

  • La configuración tiene isEnabled: false. Abre el archivo .yml junto al archivo del modelo y establece isEnabled: true.

  • La lista scripts: está vacía. Agrega los nombres de tus archivos de script:

    isEnabled: true
    scripts:
    - my_script.lua
  • El nombre del archivo .yml no coincide con el nombre del archivo del modelo. La configuración debe tener el mismo nombre base que el archivo del modelo. Por ejemplo, torch_01.fmmodel necesita torch_01.yml en el mismo directorio.


2. Archivo de script no encontrado​

Síntoma: La consola muestra: [FMM Scripts] Script 'my_script.lua' not found in scripts/ folder

Causas y soluciones:

  • Directorio incorrecto. Los archivos de script deben estar en plugins/FreeMinecraftModels/scripts/, no junto al archivo del modelo.

  • El nombre de archivo no coincide. El nombre de la configuración .yml debe coincidir con el archivo de scripts/, incluidas las mayúsculas y minúsculas. Se recomienda incluir .lua en la configuración, pero es opcional: FMM añade esa extensión internamente si la omites.

  • Archivo no presente. Verifica que el archivo .lua realmente exista en la carpeta scripts/.

  • Definición en caché o nombres repetidos. FMM busca scripts recursivamente al inicializar el proveedor, pero los vincula por nombre de archivo, no por ruta relativa. Usa nombres únicos entre subcarpetas. Después de añadir o modificar un archivo Lua, ejecuta /fmm reload y comprueba que termina correctamente; volver a generar el prop no vuelve a leer el código Lua almacenado en caché.


3. El script no se carga en absoluto​

Síntoma: No hay error en la consola, pero ningún hook se activa.

Revisa la consola del servidor en busca de errores de sintaxis Lua durante el inicio. Las causas más comunes son:

  • Falta end para cerrar una función o un bloque if
  • Paréntesis no emparejados
  • Falta coma entre definiciones de hooks en la tabla retornada
  • El archivo no termina con .lua

Los errores de descubrimiento aparecen como avisos de scripts inválidos o retirados, o como errores de carga. Los fallos de eventos y funciones programadas usan un bloque [Lua] con el archivo y el contexto de ejecución; el número de línea solo aparece si se puede extraer de la excepción.


4. El evento de script nunca se activa​

Síntoma: Aparece tu propio mensaje de inicio, pero un evento concreto nunca se activa.

Verifica que el nombre esté escrito exactamente como aparece en la referencia de eventos de script. Errores habituales:

Nombre incorrectoNombre correcto
on_clickon_right_click o on_left_click
on_interacton_right_click
on_hiton_left_click
on_tickon_game_tick
on_removeon_destroy (solo se ejecuta cuando el prop se elimina definitivamente, no al descargarse el chunk ni al recargar)
on_arrow_hiton_projectile_hit

Comprueba que el modelo se cargó, tiene una hitbox utilizable y ningún listener de protección canceló la interacción antes de la función del prop. Un prop generado usa un soporte para armaduras; cambiar una opción de configuración no permite hacer clic en un modelo inválido o sin cargar.


5. Tiempo de espera agotado / Límite de ejecución superado​

Síntoma: La consola muestra un error Lua generado dentro de la VM. Una llamada que supera el presupuesto se interrumpe durante la ejecución con uno de estos mensajes:

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)

Cada hook, callback y evaluación de archivo puede ejecutar como máximo 250.000 instrucciones Lua o consumir 50 ms de CPU del hilo actual, lo que se alcance primero. Las llamadas anidadas comparten el mismo presupuesto. Si la JVM no permite medir la CPU por hilo, se usa un límite alternativo de 250 ms de tiempo transcurrido; solo entonces aparece el tercer mensaje. El script realiza demasiado trabajo en una sola llamada. Causas comunes:

  • Iterar sobre demasiadas entidades en on_game_tick
  • Crear demasiadas partículas por tick
  • Ejecutar bucles costosos sin repartir el trabajo entre varios ticks

Solución: Limita el trabajo costoso con context.cooldowns o usa scheduler:run_repeating() con un intervalo razonable en lugar de on_game_tick.

El límite de instrucciones acota los bucles Lua. El tiempo se comprueba cada 1024 instrucciones Lua y al volver; no se interrumpe una llamada Java/Bukkit bloqueante a mitad de ejecución. Limita también el trabajo dentro de cada función programada.


6. context.event es nil en un evento de clic​

Esto normalmente no debería ocurrir para on_left_click y on_right_click, pero siempre protégete con:

if context.event then
context.event.cancel()
end

Dentro de los callbacks del scheduler, context.event siempre es nil -- este es el comportamiento esperado. La modificación de eventos solo puede ocurrir dentro del hook del evento mismo.


7. La animación no se reproduce​

Síntoma: play_animation() devuelve false, o no ocurre nada visible.

Causas y soluciones:

  • Nombre de animación incorrecto. El nombre debe coincidir exactamente con lo definido en el archivo del modelo. Revisa tu archivo .bbmodel o .fmmodel para obtener el nombre correcto de la animación.

  • El modelo no tiene animaciones. No todos los modelos tienen animaciones. Verifica que el archivo del modelo realmente contenga datos de animación.

  • El espectador no tiene el paquete o está fuera del alcance de visualización del modelo. Los jugadores necesitan tanto el paquete de recursos actual como un modelo visible.

  • La petición está en cola o se ha suprimido. Pasar true en blend usa una única posición en cola, no una transición gradual. Una petición posterior puede sustituirla y un ataque durante una animación personalizada puede devolver true sin interrumpirla. Usa los argumentos posicionales de Animaciones y distingue una petición aceptada de un fotograma mostrado.


8. Las partículas no aparecen​

  • Los nombres de partículas aceptan FLAME y flame; la API normaliza las mayúsculas antes de buscar la partícula de Bukkit.
  • Verifica que la ubicación esté en un chunk cargado. Si no hay jugadores cerca, el chunk puede estar descargado.
  • Verifica que count sea al menos 1.
  • Algunas partículas (como DUST) necesitan datos adicionales específicos que el spawn_particle() básico puede no soportar. Usa partículas estándar como FLAME, HEART, HAPPY_VILLAGER, NOTE, ENCHANT, etc.

9. El sonido no se reproduce​

  • Los sonidos aceptan nombres enum de Bukkit sin distinguir mayúsculas o claves válidas como minecraft:block.note_block.harp y claves de packs personalizados. El cliente debe disponer del sonido en su pack de recursos.
  • Verifica que las coordenadas de ubicación sean correctas (no todas cero ni NaN).
  • Verifica que el volumen sea mayor que 0.

10. El estado se reinicia inesperadamente​

context.state pertenece a una instancia de script. Las entradas separadas de scripts: del mismo prop no lo comparten. Se conserva mientras la instancia está activa. Si parece reiniciarse:

  • El prop puede haber sido eliminado y vuelto a generar, o su chunk puede haberse descargado y vuelto a cargar. Cada spawn o carga del chunk crea una nueva instancia. Guarda con prop:set_persistent_data los valores que deban sobrevivir a esto.
  • Una recarga correcta o un error de script puede haber cerrado la instancia anterior. Revisa la consola; corregir el archivo no reactiva una instancia cerrada sin recargarlo y volver a vincularlo.
  • Puede que estés leyendo el estado en un callback del scheduler usando la variable de contexto incorrecta. Usa siempre el parámetro de contexto propio del callback.

11. La biblioteca os no está disponible​

Síntoma: El script se cae con un error como attempt to index nil (os).

La librería os está completamente eliminada del sandbox de Lua. No puedes usar os.time(), os.clock() ni ninguna otra función de os.

Solución: Usa context.world:get_time() para el tiempo del mundo, o lleva la cuenta del tiempo transcurrido manualmente con marcas de estado y contadores de ticks en on_game_tick. Para los tiempos de reutilización, prefiere context.cooldowns:check_local(key, ticks).


12. El script de objeto no se activa​

El formato de scripts de objetos equipados se ha retirado. Los objetos personalizados con una lista scripts: no vacía o encantamientos antiguos sin espacio de nombres se omiten y se registran en la consola. Sustitúyelos por una definición actual de objeto personalizado y encantamientos con espacio de nombres. Los scripts de elementos decorativos siguen usando scripts:.

13. La definición del objeto no está disponible​

Consulta el aviso de inicio o recarga para conocer el archivo exacto. Un material no disponible, como un tipo de objeto de Minecraft reciente en un servidor antiguo, también provoca que se omita el objeto. Corrige o reemplaza la definición para la versión de tu servidor; FMM no la reescribe ni inventa un material sustituto.

14. El evento se ejecuta, pero el script falla al llamar a métodos de entidad​

Síntoma: Un evento como on_right_click funciona, pero el script falla dentro de una función programada con errores como attempt to call nil al llamar a entity:damage() o entity:push().

Causa: context.world:get_nearby_entities() devuelve TODAS las entidades dentro del alcance, incluidas las no vivas: objetos soltados, orbes de experiencia y nubes de efecto de área. Estas entidades no tienen métodos de entidades vivas como damage(), push() o add_potion_effect().

Solución: Protege siempre con if entity.damage then antes de llamar a métodos de entidad viva:

local entities = context.world:get_nearby_entities(x, y, z, radius)
for _, entity in ipairs(entities) do
if entity.damage then
-- Safe to call living entity methods
entity:damage(5.0)
entity:push(0, 0.5, 0)
end
end

15. Faltan datos exclusivos del evento en las funciones programadas​

Síntoma: Los datos del evento de clic/combate faltan dentro de un callback de scheduler:run_later() o scheduler:run_repeating().

Causa: Las funciones programadas reciben un contexto nuevo y no conservan el evento original de Bukkit ni el actor genérico de zona. context.event siempre es nil en ellas. Los scripts de elementos decorativos solo reciben context.player de nivel superior durante eventos provocados por un jugador, como clics y entradas o salidas de zona. Deben capturar al jugador desde context.player o context.event.player durante el evento original si lo necesitarán después.

Solución: Usa el contexto nuevo de la función programada para acceder normalmente al mundo y al estado. Si necesitas al jugador o la entidad exactos del evento original, captúralos antes de programar la función y comprueba que siguen siendo válidos.

on_right_click = function(context)
local clicked_player = context.player or (context.event and context.event.player)

context.scheduler:run_later(20, function(later_context)
-- Prop scripts should use a captured player when they need the clicker.
local player = later_context.player or clicked_player
if player and player.is_valid then
player:send_message("&aDelayed message!")
end
end)
end

Cómo funciona la generación diferida de configuración​

Entender el sistema de configuración diferida ayuda a evitar confusión al configurar nuevos props:

  1. Primer spawn: Cuando un prop aparece y no existe un archivo .yml asociado, FMM crea la configuración de forma asíncrona. Esto significa que el archivo se escribe en un hilo de fondo y no está disponible inmediatamente.

  2. Valores predeterminados: La configuración generada tiene isEnabled: true y una lista vacía scripts: [].

  3. Sin scripts en el primer spawn: Como la configuración se crea después de que el prop ya ha aparecido (y no tiene scripts listados), el prop no tendrá scripts asignados en su primer spawn.

  4. Editar, recargar y volver a generar: Añade los nombres de scripts y completa /fmm reload correctamente. Los ajustes, las comprobaciones de colocación y las listas de scripts del menú usan el mismo catálogo preparado. Un prop nuevo usa ese catálogo; no relee el YAML editado. Los cambios de Lua también requieren recarga.

  5. Ubicación de la configuración: El archivo .yml se crea en el mismo directorio que el archivo del modelo, con el mismo nombre base. Por ejemplo:

    • Modelo: plugins/FreeMinecraftModels/models/fountain.fmmodel
    • Configuración: plugins/FreeMinecraftModels/models/fountain.yml
Flujo de trabajo de configuración rápida
  1. Coloca el modelo en models/
  2. Coloca el script en scripts/
  3. Genera el prop una vez (genera el .yml)
  4. Edita el .yml para agregar scripts: [my_script.lua]
  5. Recarga para descubrir el archivo Lua, coloca un prop nuevo y comprueba su propio mensaje de inicio

Lectura de mensajes de error​

Los errores de eventos y funciones programadas generan un bloque [Lua] con el archivo y el contexto de ejecución. El número de línea se incluye si la excepción contiene una ubicación reconocible. Los errores de descubrimiento y configuración pueden usar otros avisos.

Un error típico se ve así:

[Lua] Error in 'my_door.lua' at line 12 during 'on_right_click':
[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 claro:

Error Lua sin procesarLo que la consola te indica
attempt to call nilIntentaste llamar a un método o función que no existe. Revisa errores tipográficos y el uso de : vs. ..
index expected, got nilIntentaste acceder a un campo en algo que es nil. Verifica que el código anterior lo haya inicializado.
attempt to indexIntentaste acceder a una propiedad en un valor nil o inválido.
bad argumentUna función recibió el tipo de argumento incorrecto. El mensaje muestra el tipo esperado vs. el real.
Límite superadoLua instruction budget exceeded (250000 instruction limit) o Lua CPU-time budget exceeded (50ms current-thread CPU limit): la llamada se interrumpe durante la ejecución y se desactiva la instancia del script.
consejo

Siempre lee el mensaje de error [Lua] completo antes de sumergirte en el código. Generalmente te señala directamente la solución.


No supongas que existen métodos no documentados​

La API Lua de FMM expone un conjunto específico de métodos. No asumas que existen nombres abreviados o alternativos. Errores comunes:

  • context.prop:get_location() -- no existe. Usa context.prop.current_location (un campo, no un método).
  • context.prop:set_animation("open") -- no existe. Usa context.prop:play_animation("open", true, true).
  • context.event:setCancelled(true) -- no existe. Usa context.event.cancel().
  • context.cooldowns:check_global(...) -- no existe. Usa context.cooldowns:global_ready() y context.cooldowns:set_global(ticks) para los tiempos de reutilización globales.
  • context.player -- solo está definido en los eventos de elementos decorativos que involucran a un jugador, como clics, entradas o salidas de zonas vigiladas y on_projectile_hit cuando un jugador disparó el proyectil. No es un campo genérico de propietario y será nil en los eventos del ciclo de vida y en las funciones programadas.
  • context.boss -- no existe. Eso es de EliteMobs. Usa context.prop en FMM.

Consulta los métodos admitidos en la API de Props y en las referencias compartidas que enlaza. Un nombre que parezca razonable no demuestra que el método exista.


Consejos de depuración​

1. Usa context.log:info() con frecuencia​

Agrega mensajes de registro en cada paso al depurar:

on_right_click = function(context)
context.log:info("Right click received!")
context.log:info("Event present: " .. tostring(context.event ~= nil))
context.log:info("State is_open: " .. tostring(context.state.is_open))
end

2. Revisa la consola al inicio​

Añade context.log:info("Prop script started") a on_spawn y coloca un elemento nuevo después de recargar. Busca tu mensaje y los posibles errores de script o configuración en la consola. FMM no emite una línea de éxito por cada vinculación.

3. Verifica la ruta del archivo del modelo​

La configuración .yml debe ser un archivo hermano del archivo del modelo (mismo directorio, mismo nombre base). Verifícalo revisando:

  • Ruta del archivo del modelo: plugins/FreeMinecraftModels/models/my_model.fmmodel
  • Ruta del archivo de configuración: plugins/FreeMinecraftModels/models/my_model.yml

4. Prueba los eventos de script por separado​

Al construir un script complejo, comienza probando cada hook por separado. Agrega un hook a la vez y verifica que se active antes de pasar al siguiente.

5. Busca errores tipográficos en los nombres de eventos​

La causa más común de que un evento no se ejecute es un nombre mal escrito. Un evento desconocido provoca el rechazo del script durante la validación; consulta el error de la consola en lugar de esperar que se ejecuten los demás eventos del archivo.


Progresión para principiantes​

Si quieres aprender este sistema desde cero, esta progresión funciona bien:

  1. Escribe un archivo con solo api_version = 1 y on_spawn que registre un mensaje.
  2. Agrega el script a una configuración de prop y verifica que aparezca el mensaje de registro.
  3. Agrega on_right_click y registra cuando se hace clic en el prop.
  4. Añade on_left_click con context.event.cancel() para suprimir el daño predeterminado del clic.
  5. Reproduce una animación al hacer clic derecho.
  6. Agrega un sonido al hacer clic derecho.
  7. Agrega estado y comportamiento de alternancia.
  8. Agrega una vigilancia de zona para detección de proximidad.
  9. Solo entonces pasa a scripts complejos con múltiples hooks y schedulers.

Cada paso se basa en el anterior, y puedes probar en cada etapa.


Siguientes pasos​

  • Primeros Pasos -- estructura de archivos, hooks, tutorial del primer script, plantillas para copiar y pegar
  • API de Props -- referencia completa de la API
  • Ejemplos y Patrones -- scripts funcionales completos que puedes estudiar y adaptar