Aller au contenu principal

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

  1. Choix du hook -- on_left_click se declenche quand un joueur frappe (clic gauche) le prop. En interne, il s'agit d'un EntityDamageByEntityEvent sur l'armor stand sous-jacent du prop.

  2. Verification de l'evenement -- context.event devrait toujours etre present dans ce hook, mais la verification est une bonne pratique.

  3. 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

  1. Constantes au niveau du fichier -- OPEN_ANIMATION et CLOSE_ANIMATION sont 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.

  2. Initialisation de l'etat -- on_spawn definit context.state.is_open = false. L'etat persiste a travers tous les hooks pour cette instance du prop.

  3. Invulnerabilite -- Le hook on_left_click annule les degats pour que la porte ne puisse pas etre cassee accidentellement.

  4. Logique de basculement -- on_right_click verifie context.state.is_open, arrete toute animation en cours, joue l'animation appropriee, inverse l'etat et joue un son. L'appel a stop_animation() avant play_animation() assure des transitions propres.

  5. 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.

Noms d'animation

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

  1. Constantes -- ZONE_RADIUS et PARTICLE_INTERVAL sont au niveau du fichier pour un ajustement facile.

  2. Initialisation de l'etat -- on_spawn initialise tous les champs d'etat a nil / 0 avant de faire quoi que ce soit d'autre.

  3. 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.

  4. 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 dans context.state.

  5. 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.

  6. Nettoyage -- on_destroy annule 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.

Performance des particules

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

  1. Patron de cooldown -- Etant donne que les scripts de props FMM n'ont pas d'API context.cooldowns integree comme EliteMobs, l'exemple implemente un cooldown simple en utilisant context.state.on_cooldown et scheduler:run_later(). Le drapeau est mis a true quand le son est joue, et une tache differee le reinitialise apres COOLDOWN_TICKS.

  2. 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.

  3. Retour visuel par particules -- Des particules de notes apparaissent au-dessus du prop quand le son est joue, donnant un indice visuel.

  4. Invulnerabilite -- Le hook on_left_click annule les degats comme d'habitude.

  5. Contexte du callback du scheduler -- Le callback de run_later recoit later_context, un contexte frais. Nous utilisons later_context.state (pas context.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.

Implementation du cooldown

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

  1. Demarrage automatique de l'animation -- on_spawn joue immediatement une animation "idle" en boucle. Le false pour blend signifie qu'elle demarre sans transition depuis une animation precedente. Le true pour loop signifie qu'elle se repete indefiniment.

  2. 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.

  3. Nettoyage -- on_destroy annule la tache de particules.


Bonnes pratiques

  • Commencez par un petit hook et verifiez. Ecrivez un seul on_spawn qui 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 lisez context.state.is_open dans on_right_click mais ne le definissez jamais dans on_spawn, il sera nil et vos comparaisons pourraient se comporter de maniere inattendue.

  • Annulez les taches repetitives quand c'est termine. Chaque run_repeating devrait avoir un cancel correspondant dans on_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 context externe.

  • Gardez on_game_tick leger. 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_click dans 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 context externe 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_repeating dans on_spawn mais ne l'annulez jamais, la tache s'execute jusqu'a ce que le prop soit supprime.

  • Ne pas initialiser l'etat dans on_spawn. Lire context.state.x avant de le definir retourne nil, ce qui peut casser votre logique silencieusement.

  • Mauvais noms d'animation. Si play_animation("open") retourne false, 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 :

  1. Le fichier retourne exactement une table avec api_version = 1.
  2. Chaque nom de hook correspond exactement a une entree dans la liste des hooks.
  3. context.event est verifie avec if context.event then avant d'appeler cancel().
  4. Les champs de context.state sont initialises dans on_spawn.
  5. Chaque appel a scheduler:run_repeating(...) a un scheduler:cancel(...) correspondant dans on_destroy.
  6. Les callbacks du scheduler utilisent le parametre de contexte propre au callback, pas le context externe.
  7. Les hooks on_game_tick protegent le travail couteux derriere une verification.
  8. Tous les noms de methodes existent dans la reference de l'API Prop -- pas d'alias inventes.
  9. Les noms de sons et de particules utilisent des noms d'enum UPPER_CASE de Bukkit.
  10. 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_click avec context.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