Scripting Lua: Ejemplos y patrones
Esta pagina contiene ejemplos completos y funcionales de scripts de props de FreeMinecraftModels, ademas de patrones practicos y buenas practicas. Cada ejemplo incluye una explicacion de que hace y por que.
Si eres nuevo en el scripting de props, comienza con Primeros pasos. Para detalles completos de la API, consulta la API de Props.
Ejemplo: Prop invulnerable
Lo que ensena este ejemplo: El script util mas simple -- cancelar el dano para que el prop no pueda ser destruido.
Este es el script predefinido que viene con FreeMinecraftModels.
Archivo de script completo (clic para expandir)
return {
api_version = 1,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}
Explicacion paso a paso
-
Eleccion del hook --
on_left_clickse activa cuando un jugador golpea (clic izquierdo) el prop. Internamente, se trata de unEntityDamageByEntityEventen el armor stand subyacente del prop. -
Verificacion del evento --
context.eventsiempre deberia estar presente en este hook, pero la verificacion es una buena practica. -
Cancelacion --
context.event.cancel()cancela el evento de dano, lo que evita que el armor stand reciba dano y sea destruido.
Uso
Agrega lo siguiente a la configuracion .yml de tu prop:
isEnabled: true
scripts:
- invulnerable.lua
Ejemplo: Puerta interactiva
Lo que ensena este ejemplo: Alternar estado con clic derecho, reproducir y detener animaciones, y usar context.state para rastrear si la puerta esta abierta o cerrada.
Archivo de script completo (clic para expandir)
local OPEN_ANIMATION = "open"
local CLOSE_ANIMATION = "close"
return {
api_version = 1,
on_spawn = function(context)
context.state.is_open = false
end,
on_left_click = function(context)
-- Make the door invulnerable
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
local loc = context.prop.current_location
if context.state.is_open then
-- Close the door
context.prop:stop_animation()
context.prop:play_animation(CLOSE_ANIMATION, true, false)
context.state.is_open = false
if loc then
context.world:play_sound(
"BLOCK_WOODEN_DOOR_CLOSE",
loc.x, loc.y, loc.z,
1.0, 1.0
)
end
else
-- Open the door
context.prop:stop_animation()
context.prop:play_animation(OPEN_ANIMATION, true, false)
context.state.is_open = true
if loc then
context.world:play_sound(
"BLOCK_WOODEN_DOOR_OPEN",
loc.x, loc.y, loc.z,
1.0, 1.0
)
end
end
end
}
Explicacion paso a paso
-
Constantes a nivel de archivo --
OPEN_ANIMATIONyCLOSE_ANIMATIONse definen encima de la tabla return. Esto facilita cambiarlas para diferentes archivos de modelo que puedan usar nombres de animacion distintos. -
Inicializacion del estado --
on_spawnestablececontext.state.is_open = false. El estado persiste a traves de todos los hooks para esta instancia del prop. -
Invulnerabilidad -- El hook
on_left_clickcancela el dano para que la puerta no pueda romperse accidentalmente. -
Logica de alternancia --
on_right_clickverificacontext.state.is_open, detiene cualquier animacion actual, reproduce la animacion correspondiente, cambia el estado y reproduce un sonido. La llamada astop_animation()antes deplay_animation()asegura transiciones limpias. -
Retroalimentacion sonora --
context.world:play_sound()reproduce el nombre del enum Sound de Bukkit en la ubicacion del prop. Los nombres de sonido deben ser nombres de enum en UPPER_CASE.
Los nombres de animacion como "open" y "close" deben coincidir con lo definido en el archivo del modelo. Si la animacion no se encuentra, play_animation() devuelve false y no sucede nada. Verifica tu archivo de modelo para los nombres exactos de animacion.
Ejemplo: Activador de proximidad con particulas
Lo que ensena este ejemplo: Creacion de zonas, vigilancia de zonas para eventos de entrada/salida, efectos de particulas y limpieza.
Archivo de script completo (clic para expandir)
local ZONE_RADIUS = 8
local PARTICLE_INTERVAL = 10 -- ticks between particle bursts
return {
api_version = 1,
on_spawn = function(context)
context.state.zone_handle = nil
context.state.particle_task = nil
context.state.players_in_zone = 0
local loc = context.prop.current_location
if loc == nil then return end
-- Create a sphere zone around the prop
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, ZONE_RADIUS)
context.state.zone_handle = handle
-- Watch for enter/leave
context.zones:watch(
handle,
function(player)
-- on_enter
context.state.players_in_zone = (context.state.players_in_zone or 0) + 1
end,
function(player)
-- on_leave
context.state.players_in_zone = math.max(0, (context.state.players_in_zone or 0) - 1)
end
)
-- Start a repeating particle effect at the zone boundary
context.state.particle_task = context.scheduler:run_repeating(0, PARTICLE_INTERVAL, function(tick_context)
local prop_loc = tick_context.prop.current_location
if prop_loc == nil then return end
-- Spawn particles in a ring at the zone boundary
for angle = 0, 350, 30 do
local rad = math.rad(angle)
local px = prop_loc.x + math.cos(rad) * ZONE_RADIUS
local pz = prop_loc.z + math.sin(rad) * ZONE_RADIUS
if (tick_context.state.players_in_zone or 0) > 0 then
-- Red particles when players are inside
tick_context.world:spawn_particle("DUST", px, prop_loc.y + 0.5, pz, 1, 0, 0, 0, 0)
else
-- Green particles when zone is empty
tick_context.world:spawn_particle("HAPPY_VILLAGER", px, prop_loc.y + 0.5, pz, 1, 0, 0, 0, 0)
end
end
end)
end,
on_left_click = function(context)
-- Make invulnerable
if context.event then
context.event.cancel()
end
end,
on_destroy = function(context)
-- Clean up the repeating task
if context.state.particle_task then
context.scheduler:cancel(context.state.particle_task)
context.state.particle_task = nil
end
-- Clean up the zone watch
if context.state.zone_handle then
context.zones:unwatch(context.state.zone_handle)
context.state.zone_handle = nil
end
end
}
Explicacion paso a paso
-
Constantes --
ZONE_RADIUSyPARTICLE_INTERVALestan a nivel de archivo para facilitar su ajuste. -
Inicializacion del estado --
on_spawnestablece todos los campos de estado anil/0antes de hacer cualquier otra cosa. -
Creacion de zona --
context.zones:create_sphere()crea una zona esferica centrada en el prop. El handle devuelto es un ID numerico usado para referenciar esta zona posteriormente. -
Vigilancia de zona --
context.zones:watch()registra callbacks para la entrada y salida de jugadores. Los callbacks incrementan y decrementan un contador almacenado encontext.state. -
Bucle de particulas -- Una tarea repetitiva genera particulas en un anillo alrededor del prop cada medio segundo. El tipo de particula cambia segun si hay jugadores en la zona.
-
Limpieza --
on_destroycancela la tarea repetitiva y deja de vigilar la zona. Aunque ambos se limpian automaticamente cuando el prop se elimina, la limpieza explicita es una buena practica.
Generar muchas particulas en cada tick puede afectar el rendimiento. Usa un intervalo razonable (10-20 ticks) y mantiene los conteos de particulas bajos. El ejemplo anterior usa PARTICLE_INTERVAL = 10 (dos veces por segundo) con solo 12 particulas por anillo.
Ejemplo: Prop emisor de sonido
Lo que ensena este ejemplo: Reproducir sonidos en la interaccion, comportamiento tipo cooldown usando state y scheduler, y prevencion de interacciones rapidas repetidas.
Archivo de script completo (clic para expandir)
local SOUND_NAME = "BLOCK_NOTE_BLOCK_HARP"
local COOLDOWN_TICKS = 40 -- 2 seconds between sounds
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Make invulnerable
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
-- Prevent spam
if context.state.on_cooldown then
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Play the sound
context.world:play_sound(SOUND_NAME, loc.x, loc.y, loc.z, 1.0, 1.0)
-- Show some particles
context.world:spawn_particle("NOTE", loc.x, loc.y + 1.5, loc.z, 5, 0.3, 0.3, 0.3, 0)
-- Set cooldown
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end
}
Explicacion paso a paso
-
Patron de cooldown -- Dado que los scripts de props de FMM no tienen una API
context.cooldownsintegrada como EliteMobs, el ejemplo implementa un cooldown simple usandocontext.state.on_cooldownyscheduler:run_later(). La bandera se establece entruecuando se reproduce el sonido, y una tarea retrasada la restablece despues deCOOLDOWN_TICKS. -
Reproduccion de sonido --
context.world:play_sound()recibe el nombre del enum Sound de Bukkit en UPPER_CASE, coordenadas, volumen y tono. -
Retroalimentacion con particulas -- Particulas de notas aparecen sobre el prop cuando se reproduce el sonido, proporcionando una senal visual.
-
Invulnerabilidad -- El hook
on_left_clickcancela el dano como de costumbre. -
Contexto del callback del scheduler -- El callback de
run_laterrecibelater_context, un contexto nuevo. Usamoslater_context.state(nocontext.state) para restablecer la bandera de cooldown. Como el estado es compartido, ambos apuntan a la misma tabla -- pero usar el parametro de contexto del callback es el habito correcto.
Los scripts de props de FMM no tienen la API context.cooldowns de EliteMobs. Usa el patron mostrado aqui: una bandera booleana en context.state combinada con scheduler:run_later() para restablecerla. Esto te da control total sobre la duracion y el comportamiento del cooldown.
Ejemplo: Prop ambiental animado
Lo que ensena este ejemplo: Iniciar una animacion en bucle al aparecer, con un emisor de particulas basado en ticks.
Archivo de script completo (clic para expandir)
return {
api_version = 1,
on_spawn = function(context)
-- Start the idle animation immediately, looping
context.prop:play_animation("idle", false, true)
-- Emit ambient particles every 40 ticks (2 seconds)
context.state.ambient_task = context.scheduler:run_repeating(0, 40, function(tick_context)
local loc = tick_context.prop.current_location
if loc == nil then return end
tick_context.world:spawn_particle(
"ENCHANT",
loc.x, loc.y + 1, loc.z,
10, 0.5, 0.5, 0.5, 0.05
)
end)
end,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end,
on_destroy = function(context)
if context.state.ambient_task then
context.scheduler:cancel(context.state.ambient_task)
end
end
}
Explicacion paso a paso
-
Inicio automatico de animacion --
on_spawnreproduce inmediatamente una animacion"idle"en bucle. Elfalsepara blend significa que comienza desde cero sin mezclar con una animacion anterior. Eltruepara loop significa que se repite indefinidamente. -
Particulas ambientales -- Una tarea repetitiva genera particulas de mesa de encantamientos sobre el prop cada 2 segundos, creando un efecto ambiental magico.
-
Limpieza --
on_destroycancela la tarea de particulas.
Buenas practicas
-
Empieza con un hook pequeno y verifica. Escribe un solo
on_spawnque envie un mensaje de log. Confirma que se activa. Luego construye a partir de ahi. -
Mantiene las funciones auxiliares locales. Declara los helpers como
local function toggle_door(context)encima de la tabla return. Esto los mantiene fuera del ambito global. -
Inicializa todo el estado en
on_spawn. Si leescontext.state.is_openenon_right_clickpero nunca lo estableces enon_spawn, seranily tus comparaciones pueden comportarse inesperadamente. -
Cancela las tareas repetitivas cuando terminen. Cada
run_repeatingdebe tener uncancelcorrespondiente enon_destroy. Las tareas sin cancelar desperdician CPU. -
Usa contextos frescos de callbacks del scheduler. Los callbacks del scheduler reciben un parametro de contexto nuevo. Siempre usa ese parametro dentro del callback, no el
contextexterno. -
Mantiene
on_game_tickligero. Si defines este hook, se ejecuta en cada tick del servidor (20 veces por segundo). Protege el trabajo costoso detras de una verificacion de cooldown basada en estado. -
Haz los props invulnerables por defecto. A menos que quieras que el prop sea destruible, incluye la cancelacion de dano de
on_left_clicken cada script. -
Usa UPPER_CASE para enums de Bukkit. Los nombres de sonidos y particulas deben usar el formato de constante enum de Bukkit (por ejemplo,
"FLAME", no"flame").
Errores comunes de principiantes
-
Usar el
contextexterno dentro de un callback del scheduler. El contexto externo captura una instantanea del momento en que se ejecuto el hook. Dentro de los callbacks, siempre usa el parametro propio del callback. -
Olvidar cancelar tareas repetitivas. Si inicias un
run_repeatingenon_spawnpero nunca lo cancelas, la tarea se ejecuta hasta que el prop sea eliminado. -
No inicializar el estado en
on_spawn. Leercontext.state.xantes de establecerlo devuelvenil, lo que puede romper tu logica silenciosamente. -
Nombres de animacion incorrectos. Si
play_animation("open")devuelvefalse, el nombre de la animacion no coincide con el del archivo del modelo. Verifica el modelo para los nombres exactos. -
Nombres de sonido/particulas en minusculas.
"flame"no funciona -- usa"FLAME". La API convierte a UPPER_CASE internamente para particulas, pero los nombres de enum de Sound deben ser exactos. -
Olvidar
api_version = 1. La tabla devuelta debe incluir este campo, o FMM no cargara el script. -
Poner funciones dentro de la tabla devuelta que no son hooks. Las funciones auxiliares deben declararse encima de la instruccion
return. Solo los nombres de hooks (on_spawn,on_right_click, etc.) estan permitidos como claves en la tabla devuelta.
Lista de verificacion QC
Usa esta lista de verificacion para verificar un script de prop antes de desplegarlo:
- El archivo devuelve exactamente una tabla con
api_version = 1. - Cada nombre de hook coincide exactamente con una entrada en la lista de hooks.
context.eventse verifica conif context.event thenantes de llamar acancel().- Los campos de
context.statese inicializan enon_spawn. - Cada llamada a
scheduler:run_repeating(...)tiene unscheduler:cancel(...)correspondiente enon_destroy. - Los callbacks del scheduler usan el parametro de contexto propio del callback, no el
contextexterno. - Los hooks
on_game_tickprotegen el trabajo costoso detras de una verificacion. - Todos los nombres de metodos existen en la referencia de la API de Props -- sin aliases inventados.
- Los nombres de sonidos y particulas usan nombres de enum UPPER_CASE de Bukkit.
- El script no llama a operaciones bloqueantes o de larga duracion dentro de un hook o callback.
Consejos para generacion con IA
Si quieres que una IA genere scripts de props de manera confiable, asegurate de que el prompt incluya:
- Nombre exacto del hook -- por ejemplo,
on_right_click, no "cuando el jugador hace clic en el prop". - Nombres de animacion del archivo del modelo -- la IA no puede adivinarlos; proporcionarlos.
- Nombres de enum de sonido -- por ejemplo,
"BLOCK_NOTE_BLOCK_HARP", no "sonido de arpa". - Nombres de enum de particulas -- por ejemplo,
"FLAME", no "particulas de fuego". - Si el prop debe ser invulnerable -- si es asi, incluir
on_left_clickconcontext.event.cancel(). - Usar solo nombres de metodos documentados -- si no esta en la pagina de la API de Props, no existe.
Buen ejemplo de prompt
Escribe un script de prop FMM que reproduzca la animacion "activate" al hacer clic derecho, haga el prop invulnerable, genere particulas FLAME en la ubicacion del prop al hacer clic, reproduzca el sonido BLOCK_LEVER_CLICK y tenga un cooldown de 2 segundos entre clics usando context.state y scheduler:run_later.
Proximos pasos
- Primeros pasos -- estructura de archivos, hooks, explicacion del primer script, plantillas
- API de Props -- referencia completa de la API para todas las tablas de contexto
- Solucion de problemas -- problemas comunes, consejos de depuracion