Scripts Lua : Exemples et modeles
Cette page contient des exemples complets et fonctionnels de scripts de props FreeMinecraftModels, ainsi que des modeles pratiques et des bonnes pratiques. Chaque exemple inclut une explication detaillee de ce qu'il fait et pourquoi.
Si vous debutez dans le scripting de props, commencez par Premiers pas. Pour les details complets de l'API, consultez l'API Prop.
Exemple : Prop invulnerable
Ce que cet exemple enseigne : Le script utile le plus simple -- annuler les degats pour que le prop ne puisse pas etre detruit.
Il s'agit du script pre-integre livre avec FreeMinecraftModels.
Fichier de script complet (cliquez pour developper)
return {
api_version = 1,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}
Explication pas a pas
-
Choix du hook --
on_left_clickse declenche quand un joueur frappe (clic gauche) le prop. En interne, il s'agit d'unEntityDamageByEntityEventsur l'armor stand sous-jacent du prop. -
Verification de l'evenement --
context.eventdevrait toujours etre present dans ce hook, mais la verification est une bonne pratique. -
Annulation --
context.event.cancel()annule l'evenement de degats, ce qui empeche l'armor stand de subir des degats et d'etre detruit.
Utilisation
Ajoutez a la configuration .yml de votre prop :
isEnabled: true
scripts:
- invulnerable.lua
Exemple : Porte interactive
Ce que cet exemple enseigne : Basculer l'etat au clic droit, jouer et arreter des animations, et utiliser context.state pour suivre si la porte est ouverte ou fermee.
Fichier de script complet (cliquez pour developper)
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
}
Explication pas a pas
-
Constantes au niveau du fichier --
OPEN_ANIMATIONetCLOSE_ANIMATIONsont definis au-dessus de la table return. Cela permet de les modifier facilement pour differents fichiers de modele qui pourraient utiliser des noms d'animation differents. -
Initialisation de l'etat --
on_spawndefinitcontext.state.is_open = false. L'etat persiste a travers tous les hooks pour cette instance du prop. -
Invulnerabilite -- Le hook
on_left_clickannule les degats pour que la porte ne puisse pas etre cassee accidentellement. -
Logique de basculement --
on_right_clickverifiecontext.state.is_open, arrete toute animation en cours, joue l'animation appropriee, inverse l'etat et joue un son. L'appel astop_animation()avantplay_animation()assure des transitions propres. -
Retour sonore --
context.world:play_sound()joue le nom de l'enum Sound de Bukkit a la position du prop. Les noms de sons doivent etre des noms d'enum en UPPER_CASE.
Les noms d'animation comme "open" et "close" doivent correspondre a ce qui est defini dans le fichier du modele. Si l'animation n'est pas trouvee, play_animation() retourne false et rien ne se passe. Verifiez votre fichier de modele pour les noms d'animation exacts.
Exemple : Declencheur de proximite avec particules
Ce que cet exemple enseigne : Creation de zones, surveillance de zones pour les evenements d'entree/sortie, effets de particules et nettoyage.
Fichier de script complet (cliquez pour developper)
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
}
Explication pas a pas
-
Constantes --
ZONE_RADIUSetPARTICLE_INTERVALsont au niveau du fichier pour un ajustement facile. -
Initialisation de l'etat --
on_spawninitialise tous les champs d'etat anil/0avant de faire quoi que ce soit d'autre. -
Creation de zone --
context.zones:create_sphere()cree une zone spherique centree sur le prop. Le handle retourne est un identifiant numerique utilise pour referencer cette zone par la suite. -
Surveillance de zone --
context.zones:watch()enregistre des callbacks pour l'entree et la sortie des joueurs. Les callbacks incrementent et decrementent un compteur stocke danscontext.state. -
Boucle de particules -- Une tache repetitive genere des particules en anneau autour du prop toutes les demi-secondes. Le type de particule change selon que des joueurs sont dans la zone ou non.
-
Nettoyage --
on_destroyannule la tache repetitive et arrete la surveillance de la zone. Bien que les deux soient nettoyes automatiquement quand le prop est supprime, un nettoyage explicite est une bonne pratique.
Generer beaucoup de particules a chaque tick peut impacter les performances. Utilisez un intervalle raisonnable (10-20 ticks) et gardez les quantites de particules basses. L'exemple ci-dessus utilise PARTICLE_INTERVAL = 10 (deux fois par seconde) avec seulement 12 particules par anneau.
Exemple : Prop emetteur de son
Ce que cet exemple enseigne : Jouer des sons lors d'une interaction, comportement de type cooldown utilisant state et scheduler, et prevention des interactions rapides repetees.
Fichier de script complet (cliquez pour developper)
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
}
Explication pas a pas
-
Patron de cooldown -- Etant donne que les scripts de props FMM n'ont pas d'API
context.cooldownsintegree comme EliteMobs, l'exemple implemente un cooldown simple en utilisantcontext.state.on_cooldownetscheduler:run_later(). Le drapeau est mis atruequand le son est joue, et une tache differee le reinitialise apresCOOLDOWN_TICKS. -
Lecture de son --
context.world:play_sound()prend le nom de l'enum Sound de Bukkit en UPPER_CASE, les coordonnees, le volume et la hauteur. -
Retour visuel par particules -- Des particules de notes apparaissent au-dessus du prop quand le son est joue, donnant un indice visuel.
-
Invulnerabilite -- Le hook
on_left_clickannule les degats comme d'habitude. -
Contexte du callback du scheduler -- Le callback de
run_laterrecoitlater_context, un contexte frais. Nous utilisonslater_context.state(pascontext.state) pour reinitialiser le drapeau de cooldown. Comme l'etat est partage, les deux pointent vers la meme table -- mais utiliser le parametre de contexte du callback est la bonne habitude.
Les scripts de props FMM n'ont pas l'API context.cooldowns d'EliteMobs. Utilisez le patron montre ici : un drapeau booleen dans context.state combine avec scheduler:run_later() pour le reinitialiser. Cela vous donne un controle total sur la duree et le comportement du cooldown.
Exemple : Prop ambiant anime
Ce que cet exemple enseigne : Demarrer une animation en boucle a l'apparition, avec un emetteur de particules base sur les ticks.
Fichier de script complet (cliquez pour developper)
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
}
Explication pas a pas
-
Demarrage automatique de l'animation --
on_spawnjoue immediatement une animation"idle"en boucle. Lefalsepour blend signifie qu'elle demarre sans transition depuis une animation precedente. Letruepour loop signifie qu'elle se repete indefiniment. -
Particules ambiantes -- Une tache repetitive genere des particules de table d'enchantement au-dessus du prop toutes les 2 secondes, creant un effet ambiant magique.
-
Nettoyage --
on_destroyannule la tache de particules.
Bonnes pratiques
-
Commencez par un petit hook et verifiez. Ecrivez un seul
on_spawnqui envoie un message de log. Confirmez qu'il se declenche. Puis construisez a partir de la. -
Gardez les fonctions utilitaires locales. Declarez les helpers comme
local function toggle_door(context)au-dessus de la table return. Cela les garde hors de la portee globale. -
Initialisez tout l'etat dans
on_spawn. Si vous lisezcontext.state.is_opendanson_right_clickmais ne le definissez jamais danson_spawn, il seranilet vos comparaisons pourraient se comporter de maniere inattendue. -
Annulez les taches repetitives quand c'est termine. Chaque
run_repeatingdevrait avoir uncancelcorrespondant danson_destroy. Les taches non annulees gaspillent du CPU. -
Utilisez des contextes frais de callbacks du scheduler. Les callbacks du scheduler recoivent un parametre de contexte frais. Utilisez toujours ce parametre a l'interieur du callback, pas le
contextexterne. -
Gardez
on_game_tickleger. Si vous definissez ce hook, il s'execute a chaque tick du serveur (20 fois par seconde). Protegez le travail couteux derriere une verification de cooldown basee sur l'etat. -
Rendez les props invulnerables par defaut. A moins que vous ne vouliez que le prop soit cassable, incluez l'annulation des degats de
on_left_clickdans chaque script. -
Utilisez UPPER_CASE pour les enums Bukkit. Les noms de sons et de particules doivent utiliser le format de constante enum Bukkit (par exemple
"FLAME", pas"flame").
Erreurs courantes des debutants
-
Utiliser le
contextexterne dans un callback du scheduler. Le contexte externe capture un instantane au moment ou le hook s'est execute. Dans les callbacks, utilisez toujours le parametre propre au callback. -
Oublier d'annuler les taches repetitives. Si vous demarrez un
run_repeatingdanson_spawnmais ne l'annulez jamais, la tache s'execute jusqu'a ce que le prop soit supprime. -
Ne pas initialiser l'etat dans
on_spawn. Lirecontext.state.xavant de le definir retournenil, ce qui peut casser votre logique silencieusement. -
Mauvais noms d'animation. Si
play_animation("open")retournefalse, le nom de l'animation ne correspond pas a ce qui est dans le fichier du modele. Verifiez le modele pour les noms exacts. -
Noms de sons/particules en minuscules.
"flame"ne fonctionne pas -- utilisez"FLAME". L'API convertit en UPPER_CASE en interne pour les particules, mais les noms d'enum Sound doivent etre exacts. -
Oublier
api_version = 1. La table retournee doit inclure ce champ, sinon FMM ne chargera pas le script. -
Mettre des fonctions dans la table retournee qui ne sont pas des hooks. Les fonctions utilitaires doivent etre declarees au-dessus de l'instruction
return. Seuls les noms de hooks (on_spawn,on_right_click, etc.) sont autorises comme cles dans la table retournee.
Liste de verification QC
Utilisez cette liste de verification pour verifier un script de prop avant de le deployer :
- Le fichier retourne exactement une table avec
api_version = 1. - Chaque nom de hook correspond exactement a une entree dans la liste des hooks.
context.eventest verifie avecif context.event thenavant d'appelercancel().- Les champs de
context.statesont initialises danson_spawn. - Chaque appel a
scheduler:run_repeating(...)a unscheduler:cancel(...)correspondant danson_destroy. - Les callbacks du scheduler utilisent le parametre de contexte propre au callback, pas le
contextexterne. - Les hooks
on_game_tickprotegent le travail couteux derriere une verification. - Tous les noms de methodes existent dans la reference de l'API Prop -- pas d'alias inventes.
- Les noms de sons et de particules utilisent des noms d'enum UPPER_CASE de Bukkit.
- Le script n'appelle pas d'operations bloquantes ou de longue duree dans un hook ou un callback.
Conseils pour la generation par IA
Si vous voulez qu'une IA genere des scripts de props de maniere fiable, assurez-vous que le prompt inclut :
- Nom exact du hook -- par exemple,
on_right_click, pas "quand le joueur clique sur le prop". - Noms d'animation du fichier du modele -- l'IA ne peut pas les deviner ; fournissez-les.
- Noms d'enum de son -- par exemple,
"BLOCK_NOTE_BLOCK_HARP", pas "son de harpe". - Noms d'enum de particules -- par exemple,
"FLAME", pas "particules de feu". - Si le prop doit etre invulnerable -- si oui, inclure
on_left_clickaveccontext.event.cancel(). - Utiliser uniquement des noms de methodes documentes -- si ce n'est pas sur la page de l'API Prop, ca n'existe pas.
Bon exemple de prompt
Ecris un script de prop FMM qui joue l'animation "activate" au clic droit, rend le prop invulnerable, genere des particules FLAME a la position du prop au clic, joue le son BLOCK_LEVER_CLICK et a un cooldown de 2 secondes entre les clics en utilisant context.state et scheduler:run_later.
Prochaines etapes
- Premiers pas -- structure de fichiers, hooks, explication du premier script, modeles
- API Prop -- reference complete de l'API pour toutes les tables de contexte
- Depannage -- problemes courants, conseils de debogage