Scripts Lua : Exemples et patterns
Cette page contient des exemples complets fonctionnels de scripts de props FreeMinecraftModels, ainsi que des patterns pratiques et bonnes pratiques. Chaque exemple inclut une explication détaillée de ce qu'il fait et pourquoi.
Si vous êtes nouveau dans les scripts de props, commencez par Pour commencer. Pour les détails complets de l'API, consultez l'API Prop.
Exemple : Prop invulnérable
Ce que cet exemple enseigne : Le script utile le plus simple -- annuler les dégâts pour que le prop ne puisse pas être cassé.
C'est le script pré-configuré livré avec FreeMinecraftModels.
Fichier de script complet (cliquez pour développer)
return {
api_version = 1,
on_left_click = function(context)
if context.event then
context.event.cancel()
end
end
}
Explication détaillée
-
Choix du hook --
on_left_clickse déclenche lorsqu'un joueur frappe (clic gauche) le prop. Sous le capot, c'est unEntityDamageByEntityEventsur le support d'armure du prop. -
Garde d'événement --
context.eventdevrait toujours être présent dans ce hook, mais la garde est une bonne pratique. -
Annulation --
context.event.cancel()annule l'événement de dégâts, ce qui empêche le support d'armure de prendre des dégâts et d'être détruit.
Utilisation
Ajoutez à la configuration .yml de votre prop :
isEnabled: true
scripts:
- invulnerable.lua
Exemple : Porte interactive
Ce que cet exemple enseigne : Basculer un état au clic droit, jouer et arrêter des animations, et utiliser context.state pour suivre si la porte est ouverte ou fermée.
Fichier de script complet (cliquez pour développer)
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)
-- Rendre la porte invulnérable
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
-- Fermer la porte
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
-- Ouvrir la porte
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 détaillée
-
Constantes au niveau du fichier --
OPEN_ANIMATIONetCLOSE_ANIMATIONsont définies au-dessus de la table retournée. Cela les rend faciles à modifier pour différents fichiers de modèle qui pourraient utiliser des noms d'animation différents. -
Initialisation de l'état --
on_spawndéfinitcontext.state.is_open = false. L'état persiste entre tous les hooks pour cette instance de prop. -
Invulnérabilité -- Le hook
on_left_clickannule les dégâts pour que la porte ne puisse pas être accidentellement cassée. -
Logique de basculement --
on_right_clickvérifiecontext.state.is_open, arrête toute animation en cours, joue l'animation appropriée, bascule l'état et joue un son. L'appelstop_animation()avantplay_animation()assure des transitions propres. -
Retour sonore --
context.world:play_sound()joue le nom de l'enum Bukkit Sound à la position du prop. Les noms de sons doivent être des noms d'enum en MAJUSCULES.
Les noms d'animation comme "open" et "close" doivent correspondre à ce qui est défini dans le fichier de modèle. Si l'animation n'est pas trouvée, play_animation() renvoie false et rien ne se passe. Vérifiez votre fichier de modèle pour les noms d'animation exacts.
Exemple : Déclencheur de proximité avec particules
Ce que cet exemple enseigne : Création de zones, surveillance des événements d'entrée/sortie de zone, effets de particules et nettoyage.
Fichier de script complet (cliquez pour développer)
local ZONE_RADIUS = 8
local PARTICLE_INTERVAL = 10 -- ticks entre les rafales de particules
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
-- Créer une zone sphérique autour du prop
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, ZONE_RADIUS)
context.state.zone_handle = handle
-- Surveiller les entrées/sorties
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
)
-- Démarrer un effet de particules répétitif à la limite de la zone
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
-- Faire apparaître des particules en anneau à la limite de la zone
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
-- Particules rouges quand des joueurs sont à l'intérieur
tick_context.world:spawn_particle("DUST", px, prop_loc.y + 0.5, pz, 1, 0, 0, 0, 0)
else
-- Particules vertes quand la zone est vide
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)
-- Rendre invulnérable
if context.event then
context.event.cancel()
end
end,
on_destroy = function(context)
-- Nettoyer la tâche répétitive
if context.state.particle_task then
context.scheduler:cancel(context.state.particle_task)
context.state.particle_task = nil
end
-- Nettoyer la surveillance de zone
if context.state.zone_handle then
context.zones:unwatch(context.state.zone_handle)
context.state.zone_handle = nil
end
end
}
Explication détaillée
-
Constantes --
ZONE_RADIUSetPARTICLE_INTERVALsont au niveau du fichier pour un réglage facile. -
Initialisation de l'état --
on_spawninitialise tous les champs d'état ànil/0avant de faire quoi que ce soit d'autre. -
Création de zone --
context.zones:create_sphere()crée une zone sphérique centrée sur le prop. L'identifiant renvoyé est un ID numérique utilisé pour référencer cette zone ultérieurement. -
Surveillance de zone --
context.zones:watch()enregistre des callbacks pour l'entrée et la sortie de joueurs. Les callbacks incrémentent et décrémentent un compteur stocké danscontext.state. -
Boucle de particules -- Une tâche répétitive fait apparaître des particules en anneau autour du prop chaque demi-seconde. Le type de particule change selon que des joueurs sont dans la zone ou non.
-
Nettoyage --
on_destroyannule la tâche répétitive et arrête la surveillance de la zone. Bien que les deux soient nettoyés automatiquement lorsque le prop est supprimé, un nettoyage explicite est une bonne pratique.
Faire apparaître beaucoup de particules à chaque tick peut impacter la performance. Utilisez un intervalle raisonnable (10-20 ticks) et gardez le nombre de particules bas. L'exemple ci-dessus utilise PARTICLE_INTERVAL = 10 (deux fois par seconde) avec seulement 12 particules par anneau.
Exemple : Prop émetteur de son
Ce que cet exemple enseigne : Jouer des sons lors d'interactions, comportement de type temps de recharge en utilisant l'état et le scheduler, et empêcher les interactions rapides.
Fichier de script complet (cliquez pour développer)
local SOUND_NAME = "BLOCK_NOTE_BLOCK_HARP"
local COOLDOWN_TICKS = 40 -- 2 secondes entre les sons
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Rendre invulnérable
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
-- Empêcher le spam
if context.state.on_cooldown then
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Jouer le son
context.world:play_sound(SOUND_NAME, loc.x, loc.y, loc.z, 1.0, 1.0)
-- Afficher des particules
context.world:spawn_particle("NOTE", loc.x, loc.y + 1.5, loc.z, 5, 0.3, 0.3, 0.3, 0)
-- Définir le temps de recharge
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end
}
Explication détaillée
-
Pattern de temps de recharge -- Comme les scripts de props FMM n'ont pas d'API intégrée
context.cooldownscomme EliteMobs, l'exemple implémente un simple temps de recharge en utilisantcontext.state.on_cooldownetscheduler:run_later(). Le drapeau est mis àtruelorsque le son joue, et une tâche différée le réinitialise aprèsCOOLDOWN_TICKS. -
Lecture de son --
context.world:play_sound()prend le nom de l'enum Bukkit Sound en MAJUSCULES, les coordonnées, le volume et la hauteur tonale. -
Retour visuel par particules -- Des particules de notes apparaissent au-dessus du prop lorsque le son joue, donnant un indice visuel.
-
Invulnérabilité -- Le hook
on_left_clickannule les dégâts comme d'habitude. -
Contexte du callback de scheduler -- Le callback
run_laterreçoitlater_context, un contexte frais. Nous utilisonslater_context.state(pascontext.state) pour réinitialiser le drapeau de temps de recharge. Comme l'état est partagé, les deux pointent vers la même table -- mais utiliser le paramètre de contexte du callback est la bonne habitude.
Les scripts de props FMM n'ont pas l'API context.cooldowns d'EliteMobs. Utilisez le pattern montré ici : un drapeau booléen dans context.state combiné avec scheduler:run_later() pour le réinitialiser. Cela vous donne un contrôle total sur la durée et le comportement du temps de recharge.
Exemple : Prop ambiant animé
Ce que cet exemple enseigne : Démarrer une animation en boucle au spawn, avec un émetteur de particules basé sur les ticks.
Fichier de script complet (cliquez pour développer)
return {
api_version = 1,
on_spawn = function(context)
-- Démarrer l'animation idle immédiatement, en boucle
context.prop:play_animation("idle", false, true)
-- Émettre des particules ambiantes toutes les 40 ticks (2 secondes)
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 détaillée
-
Animation auto-démarrée --
on_spawnjoue immédiatement une animation"idle"en boucle. Lefalsepour blend signifie qu'elle démarre de zéro sans fusionner avec une animation précédente. Letruepour loop signifie qu'elle se répète indéfiniment. -
Particules ambiantes -- Une tâche répétitive fait apparaître des particules de table d'enchantement au-dessus du prop toutes les 2 secondes, créant un effet ambiant magique.
-
Nettoyage --
on_destroyannule la tâche de particules.
Exemple : Chaise pour s'asseoir
Ce que cet exemple enseigne : Monter un joueur sur un prop par clic droit, descendre par clic gauche, et utiliser context.event.player pour obtenir le joueur interagissant.
Fichier de script complet (cliquez pour développer)
return {
api_version = 1,
on_right_click = function(context)
local player = context.event and context.event.player
if not player then return end
-- Vérifier si le joueur est déjà assis sur ce prop
local passengers = context.prop:get_passengers()
for i = 1, #passengers do
if passengers[i].uuid == player.uuid then
-- Le joueur est déjà assis, ne rien faire au clic droit
return
end
end
-- Monter le joueur sur la chaise
context.prop:mount(player)
local loc = context.prop.current_location
if loc then
context.world:play_sound("BLOCK_WOOD_PLACE", loc.x, loc.y, loc.z, 0.8, 1.2)
end
end,
on_left_click = function(context)
-- Annuler les dégâts pour que la chaise soit invulnérable
if context.event then
context.event.cancel()
end
local player = context.event and context.event.player
if not player then return end
-- Vérifier si le joueur est assis et le faire descendre
local passengers = context.prop:get_passengers()
for i = 1, #passengers do
if passengers[i].uuid == player.uuid then
context.prop:dismount(player)
local loc = context.prop.current_location
if loc then
context.world:play_sound("BLOCK_WOOD_BREAK", loc.x, loc.y, loc.z, 0.8, 1.0)
end
return
end
end
end
}
Explication détaillée
-
Clic droit pour s'asseoir --
on_right_clickobtient le joueur depuiscontext.event.player, vérifie s'il est déjà passager (pour éviter le double montage), et appellecontext.prop:mount(player)pour l'asseoir sur le support d'armure du prop. -
Clic gauche pour se lever --
on_left_clickannule l'événement de dégâts (invulnérabilité), puis vérifie si le joueur qui frappe est actuellement passager. Si oui,context.prop:dismount(player)l'éjecte. -
Vérification des passagers --
context.prop:get_passengers()renvoie un tableau de tables d'entités. Nous comparons les UUID pour trouver le joueur interagissant dans la liste. -
Retour sonore -- Un son de placement de bois joue en s'asseyant et un son de cassure de bois en se levant, donnant un retour tactile.
Exemple : Sanctuaire de bénédiction
Ce que cet exemple enseigne : Vérifier l'objet tenu par le joueur, consommer des objets, appliquer des effets de potion aléatoires, gérer le temps de recharge, et retour par particules/son.
Fichier de script complet (cliquez pour développer)
local COOLDOWN_TICKS = 600 -- 30 secondes entre les utilisations
local BLESSINGS = {
{ effect = "speed", name = "Rapidité" },
{ effect = "strength", name = "Force" },
{ effect = "regeneration", name = "Régénération" },
{ effect = "resistance", name = "Résistance" },
{ effect = "jump_boost", name = "Bond" },
{ effect = "haste", name = "Célérité" },
}
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Rendre invulnérable
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
local player = context.event and context.event.player
if not player then return end
-- Vérifier le temps de recharge
if context.state.on_cooldown then
player:send_message("&eLe sanctuaire se recharge... Veuillez patienter.")
return
end
-- Vérifier si le joueur tient un lingot d'or
local held = player:get_held_item()
if not held or held.type ~= "gold_ingot" then
player:send_message("&eLe sanctuaire exige une offrande en or...")
return
end
-- Consommer un lingot d'or
player:consume_held_item(1)
-- Jouer les effets de bénédiction
local loc = context.prop.current_location
if loc then
context.world:spawn_particle("ENCHANT", loc.x, loc.y + 1.5, loc.z, 30, 0.5, 0.5, 0.5, 0.5)
context.world:spawn_particle("HAPPY_VILLAGER", loc.x, loc.y + 1, loc.z, 10, 0.3, 0.3, 0.3, 0)
context.world:play_sound("BLOCK_BEACON_ACTIVATE", loc.x, loc.y, loc.z, 1.0, 1.5)
end
-- Appliquer une bénédiction aléatoire
local chosen = BLESSINGS[math.random(#BLESSINGS)]
player:add_potion_effect(chosen.effect, 600, 1) -- 30 secondes, niveau II
player:send_message("&aLe sanctuaire vous bénit avec " .. chosen.name .. " !")
-- Définir le temps de recharge
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end
}
Explication détaillée
-
Vérification de l'objet --
player:get_held_item()renvoie une table avectype,amountetdisplay_namepour l'objet en main principale (ounilsi vide). Nous comparonsheld.typeavec"gold_ingot"(nom de matériau en minuscules). -
Consommation d'objet --
player:consume_held_item(1)retire un objet de la pile en main principale du joueur. -
Buff aléatoire -- La table
BLESSINGSau niveau du fichier liste les effets positifs disponibles.math.random(#BLESSINGS)en choisit un au hasard.player:add_potion_effect(effect, duration, amplifier)l'applique --600ticks font 30 secondes, l'amplificateur1est le niveau II. -
Temps de recharge -- Le même pattern de drapeau booléen plus scheduler que l'exemple du prop émetteur de son. Un temps de recharge de 30 secondes empêche le spam du sanctuaire.
-
Retour visuel -- Des particules d'enchantement et de villageois heureux plus un son d'activation de balise créent une ambiance de « bénédiction divine ».
Exemple : Sanctuaire maudit
Ce que cet exemple enseigne : Effets de potion négatifs, coups de foudre, apparition d'entités et logique conditionnelle basée sur les offrandes du joueur.
Fichier de script complet (cliquez pour développer)
local COOLDOWN_TICKS = 600 -- 30 secondes entre les utilisations
local BUFFS = {
{ effect = "speed", name = "Rapidité" },
{ effect = "strength", name = "Force" },
{ effect = "regeneration", name = "Régénération" },
{ effect = "resistance", name = "Résistance" },
}
local CURSES = {
{ effect = "slowness", name = "Lenteur" },
{ effect = "weakness", name = "Faiblesse" },
{ effect = "poison", name = "Poison" },
{ effect = "mining_fatigue", name = "Fatigue minière" },
}
local ZOMBIE_COUNT = 4
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Rendre invulnérable
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
local player = context.event and context.event.player
if not player then return end
-- Vérifier le temps de recharge
if context.state.on_cooldown then
player:send_message("&7Le sanctuaire sombre pulse d'énergie résiduelle...")
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Vérifier si le joueur tient un lingot d'or
local held = player:get_held_item()
if not held or held.type ~= "gold_ingot" then
-- Pas d'offrande -- punir le joueur !
player:send_message("&4Le sanctuaire exige un tribut ! Vous osez approcher les mains vides ?!")
-- Frapper le joueur avec la foudre
local player_loc = player.current_location
if player_loc then
context.world:strike_lightning(player_loc.x, player_loc.y, player_loc.z)
end
-- Faire apparaître une horde de zombies autour du sanctuaire
for i = 1, ZOMBIE_COUNT do
local angle = math.rad((360 / ZOMBIE_COUNT) * i)
local spawn_x = loc.x + math.cos(angle) * 3
local spawn_z = loc.z + math.sin(angle) * 3
context.world:spawn_entity("zombie", spawn_x, loc.y, spawn_z)
end
-- Appliquer une malédiction aléatoire
local chosen_curse = CURSES[math.random(#CURSES)]
player:add_potion_effect(chosen_curse.effect, 400, 1) -- 20 secondes, niveau II
player:send_message("&cLe sanctuaire vous maudit avec " .. chosen_curse.name .. " !")
-- Effets sinistres
context.world:spawn_particle("SMOKE", loc.x, loc.y + 1, loc.z, 30, 0.5, 0.5, 0.5, 0.05)
context.world:play_sound("ENTITY_WITHER_AMBIENT", loc.x, loc.y, loc.z, 1.0, 0.5)
else
-- Or offert -- récompenser le joueur
player:consume_held_item(1)
-- Appliquer un buff aléatoire
local chosen_buff = BUFFS[math.random(#BUFFS)]
player:add_potion_effect(chosen_buff.effect, 600, 1) -- 30 secondes, niveau II
player:send_message("&aLe sanctuaire sombre accepte votre offrande. Vous êtes béni avec " .. chosen_buff.name .. " !")
-- Retour positif
context.world:spawn_particle("ENCHANT", loc.x, loc.y + 1.5, loc.z, 30, 0.5, 0.5, 0.5, 0.5)
context.world:play_sound("BLOCK_BEACON_ACTIVATE", loc.x, loc.y, loc.z, 1.0, 0.8)
end
-- Définir le temps de recharge quel que soit le chemin
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end
}
Explication détaillée
-
Branchement sur l'offrande -- Le script vérifie
player:get_held_item()et prend l'un des deux chemins : punition si le joueur n'a pas d'or, ou récompense s'il en a. -
Coup de foudre --
context.world:strike_lightning(x, y, z)frappe une foudre réelle (infligeant des dégâts) à la position du joueur. La position du joueur est lue depuisplayer.current_location. -
Apparition de zombies --
context.world:spawn_entity("zombie", x, y, z)fait apparaître des zombies vanilla. La boucle les distribue uniformément en cercle autour du sanctuaire en utilisant la trigonométrie. -
Effets de potion négatifs --
player:add_potion_effect("poison", 400, 1)applique 20 secondes de Poison II. Les noms d'effets sont des chaînes en minuscules correspondant aux nomsPotionEffectTypede Bukkit. -
Chemin de récompense -- Lorsque de l'or est offert, le sanctuaire consomme un lingot et applique un effet positif aléatoire, reflétant le comportement du sanctuaire de bénédiction.
-
Temps de recharge -- Un temps de recharge de 30 secondes s'applique quel que soit le chemin pris, empêchant la punition ou la récompense rapide.
Faire apparaître plusieurs entités d'un coup peut impacter la performance du serveur. Gardez le nombre bas (4-6) et envisagez d'ajouter un temps de recharge par joueur si beaucoup de joueurs utilisent le sanctuaire simultanément.
Exemple : Globe tournant
Ce que cet exemple enseigne : Jouer une animation minutée lors d'une interaction, planifier l'arrêt d'une animation, et effets sonores mécaniques.
Fichier de script complet (cliquez pour développer)
local SPIN_ANIMATION = "spin"
local SPIN_DURATION = 100 -- 5 secondes en ticks
return {
api_version = 1,
on_spawn = function(context)
context.state.is_spinning = false
end,
on_left_click = function(context)
-- Rendre invulnérable
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
-- Empêcher de démarrer une nouvelle rotation pendant que ça tourne déjà
if context.state.is_spinning then
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Démarrer l'animation de rotation (sans boucle)
context.prop:play_animation(SPIN_ANIMATION, true, false)
context.state.is_spinning = true
-- Jouer un son de clic mécanique
context.world:play_sound("BLOCK_CHAIN_PLACE", loc.x, loc.y, loc.z, 1.0, 1.5)
-- Planifier l'arrêt de l'animation après 5 secondes
context.scheduler:run_later(SPIN_DURATION, function(later_context)
later_context.prop:stop_animation()
later_context.state.is_spinning = false
local stop_loc = later_context.prop.current_location
if stop_loc then
later_context.world:play_sound("BLOCK_CHAIN_FALL", stop_loc.x, stop_loc.y, stop_loc.z, 1.0, 0.8)
end
end)
end
}
Explication détaillée
-
Garde d'état --
context.state.is_spinningempêche les demandes de rotation multiples qui se chevaucheraient. Le drapeau est activé au début de la rotation et désactivé quand l'arrêt planifié se déclenche. -
Animation minutée --
play_animation(SPIN_ANIMATION, true, false)joue l'animation une fois (sans boucle). L'appelscheduler:run_later(100, ...)arrête l'animation après exactement 5 secondes, au cas où l'animation elle-même serait plus longue ou en boucle. -
Sons mécaniques --
BLOCK_CHAIN_PLACEdonne un son de démarrage mécanique/clic ;BLOCK_CHAIN_FALLdonne un son d'arrêt/ralentissement. Ajustez la hauteur tonale selon vos goûts. -
Contexte du callback -- Le callback
run_laterutiliselater_context(pascontext) pour tout accès à l'état et au monde. C'est le pattern correct pour les callbacks de scheduler.
Le nom d'animation "spin" doit correspondre à ce qui est défini dans votre fichier de modèle. Si votre modèle utilise un nom différent (ex : "rotate", "turn"), mettez à jour la constante SPIN_ANIMATION en conséquence.
Exemple : Prop jumpscare
Ce que cet exemple enseigne : Déclencheurs de zone de proximité, effets de peur ponctuels avec un long temps de recharge, et combinaison de son/particules/animation pour un impact dramatique.
Fichier de script complet (cliquez pour développer)
local SCARE_RADIUS = 3
local COOLDOWN_TICKS = 1200 -- 60 secondes entre les peurs
local SCARE_ANIMATION = "jumpscare"
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
context.state.zone_handle = nil
local loc = context.prop.current_location
if loc == nil then return end
-- Créer une petite zone sphérique autour du prop
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, SCARE_RADIUS)
context.state.zone_handle = handle
-- Surveiller les joueurs entrant dans la zone
context.zones:watch(
handle,
function(player)
-- on_enter : déclencher la peur
if context.state.on_cooldown then
return
end
local scare_loc = context.prop.current_location
if scare_loc == nil then return end
-- Jouer l'animation de jumpscare
context.prop:stop_animation()
context.prop:play_animation(SCARE_ANIMATION, false, false)
-- Son effrayant
context.world:play_sound(
"ENTITY_GHAST_SCREAM",
scare_loc.x, scare_loc.y, scare_loc.z,
1.0, 0.7
)
-- Rafale de particules de fumée
context.world:spawn_particle(
"CAMPFIRE_SIGNAL_SMOKE",
scare_loc.x, scare_loc.y + 1, scare_loc.z,
20, 0.5, 0.5, 0.5, 0.05
)
-- Définir le temps de recharge pour ne pas se redéclencher immédiatement
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end,
nil -- pas de callback on_leave nécessaire
)
end,
on_left_click = function(context)
-- Rendre invulnérable
if context.event then
context.event.cancel()
end
end,
on_destroy = function(context)
-- Nettoyer la surveillance de zone
if context.state.zone_handle then
context.zones:unwatch(context.state.zone_handle)
context.state.zone_handle = nil
end
end
}
Explication détaillée
-
Zone de proximité --
context.zones:create_sphere()crée une zone de rayon 3 blocs.context.zones:watch()enregistre un callback d'entrée qui se déclenche quand n'importe quel joueur entre dedans. -
Effets de peur -- Le callback d'entrée joue une animation
"jumpscare", un cri de ghast et fait apparaître des particules de fumée de feu de camp. La combinaison crée un effet soudain et surprenant. -
Temps de recharge de 60 secondes -- Le pattern de drapeau booléen empêche la peur de se déclencher de manière répétée. Une fois déclenché, le prop reste silencieux pendant 60 secondes (
COOLDOWN_TICKS = 1200), puis se réarme. -
Pas de callback de sortie -- L'argument
nilsecond decontext.zones:watch()signifie que nous ne nous soucions pas de quand les joueurs quittent la zone. -
Nettoyage --
on_destroyarrête la surveillance de la zone. Bien que les zones soient nettoyées automatiquement lorsque le prop est supprimé, un nettoyage explicite est une bonne pratique.
Pour un meilleur effet de jumpscare, cachez le prop derrière un coin ou dans une zone sombre. Le rayon de 3 blocs assure que le joueur est proche avant que la peur ne se déclenche. Ajustez SCARE_RADIUS et COOLDOWN_TICKS selon vos goûts.
Exemple : Prop invocateur de gobelin
Ce que cet exemple enseigne : Utiliser prop:spawn_elitemobs_boss() pour faire apparaître un boss personnalisé depuis une interaction de prop, avec un repli gracieux si EliteMobs n'est pas installé.
Fichier de script complet (cliquez pour développer)
local BOSS_FILE = "goblin_warrior.yml"
local COOLDOWN_TICKS = 200 -- 10 secondes entre les apparitions
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Rendre invulnérable
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
local player = context.event and context.event.player
if not player then return end
-- Empêcher le spam d'apparition
if context.state.on_cooldown then
player:send_message("&7L'invocateur se recharge...")
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Essayer de faire apparaître le boss EliteMobs
local boss = context.prop:spawn_elitemobs_boss(BOSS_FILE, loc.x, loc.y + 1, loc.z)
if boss then
-- Succès -- jouer les effets d'apparition
context.world:spawn_particle("FLAME", loc.x, loc.y + 1, loc.z, 20, 0.5, 0.5, 0.5, 0.05)
context.world:play_sound("ENTITY_EVOKER_PREPARE_SUMMON", loc.x, loc.y, loc.z, 1.0, 1.0)
player:send_message("&cUn guerrier gobelin émerge !")
else
-- EliteMobs n'est pas installé ou le fichier de boss n'a pas été trouvé
context.log:warn("Impossible de faire apparaître le boss '" .. BOSS_FILE .. "' -- EliteMobs est-il installé ?")
player:send_message("&7L'invocateur grésille... (EliteMobs non disponible)")
-- Particules de grésillement comme retour visuel
context.world:spawn_particle("SMOKE", loc.x, loc.y + 1, loc.z, 10, 0.3, 0.3, 0.3, 0.02)
context.world:play_sound("BLOCK_FIRE_EXTINGUISH", loc.x, loc.y, loc.z, 0.8, 1.2)
end
-- Définir le temps de recharge
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end
}
Explication détaillée
-
Apparition de boss --
context.prop:spawn_elitemobs_boss(filename, x, y, z)fait apparaître un boss personnalisé EliteMobs aux coordonnées données. Le nom de fichier doit correspondre à un fichier.ymldans le dossiercustombossesd'EliteMobs. -
Repli gracieux --
spawn_elitemobs_boss()renvoienilsi EliteMobs n'est pas installé ou si le fichier de boss n'existe pas. Le script gère cela avec un message de log d'avertissement, un effet de particules de grésillement et un message au joueur expliquant l'échec. -
Décalage d'apparition -- Le boss apparaît à
loc.y + 1(un bloc au-dessus du prop) pour empêcher le boss de passer à travers le prop ou le sol. -
Temps de recharge -- Un temps de recharge de 10 secondes empêche les joueurs d'inonder la zone de guerriers gobelins. Ajustez
COOLDOWN_TICKSselon vos besoins de gameplay. -
Distinction visuelle/audio -- Le succès utilise des particules de flamme et un son d'invocation d'évocateur pour une apparition dramatique. L'échec utilise de la fumée et un son d'extinction de feu pour un effet de « grésillement » clair, afin que le joueur sache que quelque chose a mal tourné sans vérifier la console.
Le nom de fichier du boss (ex : "goblin_warrior.yml") doit correspondre à une configuration de boss personnalisé existante dans EliteMobs. Si vous distribuez une carte ou un donjon qui utilise ce script, incluez le fichier de configuration du boss et documentez la dépendance à EliteMobs.
Bonnes pratiques
-
Commencez avec un petit hook et vérifiez. Écrivez un seul
on_spawnqui envoie un message de log. Confirmez qu'il se déclenche. Puis construisez à partir de là. -
Gardez les fonctions utilitaires locales. Déclarez les utilitaires comme
local function toggle_door(context)au-dessus de la table retournée. Cela les garde hors de la portée globale. -
Initialisez tout l'état dans
on_spawn. Si vous lisezcontext.state.is_opendanson_right_clickmais ne le définissez jamais danson_spawn, il seranilet vos comparaisons peuvent se comporter de manière inattendue. -
Annulez les tâches répétitives quand elles sont terminées. Chaque
run_repeatingdevrait avoir uncancelcorrespondant danson_destroy. Les tâches qui fuient gaspillent du CPU. -
Utilisez les contextes frais des callbacks de scheduler. Les callbacks de scheduler reçoivent un paramètre de contexte frais. Utilisez toujours ce paramètre à l'intérieur du callback, pas le
contextexterne. -
Gardez
on_game_tickléger. Si vous définissez ce hook, il s'exécute à chaque tick serveur (20 fois par seconde). Gardez le travail coûteux derrière une vérification de temps de recharge basée sur l'état. -
Rendez les props invulnérables par défaut. Sauf si vous voulez que le prop soit cassable, incluez l'annulation des dégâts
on_left_clickdans chaque script. -
Utilisez les MAJUSCULES pour les enums Bukkit. Les noms de sons et de particules doivent utiliser le format de constante d'enum Bukkit (ex :
"FLAME", pas"flame").
Erreurs courantes de débutant
-
Utiliser le
contextexterne dans un callback de scheduler. Le contexte externe capture un instantané au moment où le hook s'est exécuté. Dans les callbacks, utilisez toujours le paramètre propre du callback. -
Oublier d'annuler les tâches répétitives. Si vous démarrez un
run_repeatingdanson_spawnmais ne l'annulez jamais, la tâche s'exécute jusqu'à la suppression du prop. -
Ne pas initialiser l'état dans
on_spawn. Lirecontext.state.xavant de le définir renvoienil, ce qui peut casser votre logique silencieusement. -
Mauvais noms d'animation. Si
play_animation("open")renvoiefalse, le nom de l'animation ne correspond pas à ce qui est dans le fichier de modèle. Vérifiez le modèle pour les noms exacts. -
Noms de sons/particules en minuscules.
"flame"ne fonctionne pas -- utilisez"FLAME". L'API convertit en MAJUSCULES en interne pour les particules, mais les noms d'enum Sound doivent être exacts. -
Oublier
api_version = 1. La table retournée doit inclure ce champ, sinon FMM ne chargera pas le script. -
Mettre des fonctions dans la table retournée qui ne sont pas des hooks. Les fonctions utilitaires doivent être déclarées au-dessus de l'instruction
return. Seuls les noms de hooks (on_spawn,on_right_click, etc.) sont autorisés comme clés dans la table retournée.
Liste de vérification QC
Utilisez cette liste pour vérifier un script de prop avant de le déployer :
- Le fichier retourne exactement une table avec
api_version = 1. - Chaque nom de hook correspond exactement à une entrée dans la liste des hooks.
context.eventest protégé avecif context.event thenavant d'appelercancel().- Les champs de
context.statesont initialisés danson_spawn. - Chaque appel
scheduler:run_repeating(...)a unscheduler:cancel(...)correspondant danson_destroy. - Les callbacks de scheduler utilisent le paramètre de contexte propre du callback, pas le
contextexterne. - Les hooks
on_game_tickgardent le travail coûteux derrière une vérification. - Tous les noms de méthodes existent dans la référence API Prop -- pas d'alias inventés.
- Les noms de sons et de particules utilisent les noms d'enum Bukkit en MAJUSCULES.
- Le script n'appelle aucune opération bloquante ou de longue durée dans un hook ou callback.
Conseils pour la génération par IA
Si vous voulez que l'IA génère des scripts de props de manière fiable, assurez-vous que le prompt inclut :
- Nom exact du hook -- ex :
on_right_click, pas « quand le joueur clique sur le prop ». - Noms d'animation du fichier de modèle -- l'IA ne peut pas les deviner ; fournissez-les.
- Noms d'enum Sound -- ex :
"BLOCK_NOTE_BLOCK_HARP", pas « son de harpe ». - Noms d'enum Particle -- ex :
"FLAME", pas « particules de feu ». - Si le prop doit être invulnérable -- si oui, incluez
on_left_clickaveccontext.event.cancel(). - N'utilisez que les noms de méthodes documentés -- si ce n'est pas sur la page API Prop, ça n'existe pas.
Bon exemple de prompt
Écris un script de prop FMM qui joue l'animation "activate" au clic droit, rend le prop invulnérable, fait apparaître des particules FLAME à la position du prop au clic, joue le son BLOCK_LEVER_CLICK, et a un temps de recharge de 2 secondes entre les clics en utilisant context.state et scheduler:run_later.
Prochaines étapes
- Pour commencer -- structure de fichiers, hooks, guide du premier script, modèles
- API Prop -- référence API complète pour toutes les tables de contexte
- Dépannage -- problèmes courants, astuces de débogage