Scripts Lua : exemples et modèles
Cette page présente des exemples de scripts de props FreeMinecraftModels avec leurs explications. Fournissez les animations et le contenu référencés, puis vérifiez chaque exemple sur votre serveur avant de l'utiliser dans une carte.
Si vous débutez avec les scripts, consultez Premiers pas. Pour les détails complets de l'API, consultez l'API des props.
Exemple : prop invulnérable
Ce que cet exemple enseigne : Le script utile le plus simple : annuler les dégâts de clic gauche par défaut.
Enregistrez ce script sous invulnerable.lua dans le dossier des scripts et rechargez avant de l'associer à un prop.
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 le prop par clic gauche. L'événement Lua est l'indicateur d'annulation de FMM pour son traitement par défaut des dégâts au 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()empêche FMM d'appliquer les dégâts par défaut de ce clic. Cela ne rend pas le prop invulnérable à toutes les sources de dégâts.
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)
-- Prevent default left-click damage
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 détaillée
-
Constantes à la portée du fichier --
OPEN_ANIMATIONetCLOSE_ANIMATIONsont définies au-dessus de la table de retour. 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 les hooks de cette association de script à ce prop. Les autres scripts du même prop ont un état distinct. -
Protection contre les clics -- Le hook
on_left_clickannule les dégâts par défaut de ce clic. -
Logique de bascule --
on_right_clickdemande un arrêt puis l'animation appropriée, inverse l'indicateur et joue un son. L'indicateur représente l'état demandé, pas une lecture confirmée.blend = truemet l'animation en file d'attente ; l'arrêt ne remplace pas une animation de mort active du moteur partagé. Consultez Animations. Cet exemple anime le modèle, mais ne modifie pas les collisions des barrières et ne garantit pas que l'ouverture reste franchissable. -
Les sons acceptent les noms enum Bukkit sans distinction de casse ou des clés valides comme
minecraft:block.note_block.harpet les clés des packs personnalisés. Le client doit disposer du son dans son pack de ressources.
Les noms comme "open" et "close" doivent correspondre au modèle. Une animation absente renvoie false ; cet exemple modifie quand même l'indicateur et joue le son. Traitez le résultat si ces effets doivent dépendre de l'acceptation, et rappelez-vous que true ne prouve pas une lecture visible.
Exemple : déclencheur de proximité avec particules
Ce que cet exemple enseigne : Création de zone, surveillance de zone pour les événements d'entrée/sortie, effets de particules et nettoyage.
Fichier de script complet (cliquez pour développer)
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
-- Pass placeholder functions to enable on_zone_enter and on_zone_leave.
-- The actual logic lives in the hooks below.
context.zones:watch(handle, function() end, function() 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
-- Flame particles when players are inside
tick_context.world:spawn_particle("FLAME", 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)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_zone_enter = function(context)
context.state.players_in_zone = (context.state.players_in_zone or 0) + 1
end,
on_zone_leave = function(context)
context.state.players_in_zone = math.max(0, (context.state.players_in_zone or 0) - 1)
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 détaillée
-
Constantes --
ZONE_RADIUSetPARTICLE_INTERVALsont à la portée du fichier pour un ajustement 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 sphère à la position du prop lors de son apparition. L'identifiant numérique référence cette zone fixe. L'anneau de particules lit la position actuelle du prop : utilisez donc un prop immobile ou actualisez la zone lorsque vous le déplacez. -
Surveillance de zone --
context.zones:watch(handle, function() end, function() end)active les hookson_zone_entereton_zone_leave. Ces hooks incrémentent et décrémentent un compteur stocké danscontext.state; ils reçoivent aussi le joueur qui traverse viacontext.player/context.event.playersi vous voulez un comportement spécifique au joueur. -
Boucle de particules -- Une tâche répétée produit l'anneau tous les 10 ticks serveur, soit une demi-seconde à 20 TPS. Le type de particule dépend de la présence de joueurs dans la zone.
-
Nettoyage --
on_destroyannule la tâche répétée et arrête la surveillance de la zone. Bien que les deux soient nettoyés automatiquement lorsque le prop est retiré, le nettoyage explicite est une bonne pratique.
Faire apparaître de nombreuses particules à chaque tick peut impacter les performances. 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 à l'interaction, utiliser context.cooldowns et empêcher les interactions en rafale.
Fichier de script complet (cliquez pour développer)
local SOUND_NAME = "BLOCK_NOTE_BLOCK_HARP"
local COOLDOWN_TICKS = 40 -- 2 seconds between sounds
return {
api_version = 1,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
-- Prevent spam
if not context.cooldowns:check_local("sound", COOLDOWN_TICKS) 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)
end
}
Explication détaillée
-
Temps de recharge --
context.cooldowns:check_local("sound", COOLDOWN_TICKS)vérifie le délai et le démarre immédiatement s'il est prêt. Tous les joueurs utilisant cette association de script sur ce prop le partagent. Ces ticks représentent chacun 50 ms écoulées ; les délais du planificateur comptent de véritables ticks serveur. -
Les sons acceptent les noms enum Bukkit sans distinction de casse ou des clés valides comme
minecraft:block.note_block.harpet les clés des packs personnalisés. Le client doit disposer du son dans son pack de ressources. -
Retour par particules -- Des particules de note apparaissent au-dessus du prop lorsque le son est joué, donnant un indice visuel.
-
Protection contre les clics -- Le hook
on_left_clickannule les dégâts par défaut de ce clic.
Utilisez context.cooldowns pour les cooldowns normaux. Réservez context.state plus scheduler:run_later() pour les cas où vous avez besoin de changements d'état personnalisés à la fin du délai.
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)
-- 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 détaillée
-
Démarrage automatique de l'animation --
on_spawnjoue immédiatement une animation"idle"en boucle. La valeurfalsede blend provoque un changement immédiat. L'état intégréidleboucle quelle que soit la valeur de l'argument loop. -
Particules ambiantes -- Une tâche répétée 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 : Faire asseoir un joueur sur un prop au clic droit, le faire descendre au clic gauche et utiliser context.event.player pour obtenir le joueur qui interagit.
Fichier de script complet (cliquez pour développer)
return {
api_version = 1,
on_right_click = function(context)
-- Handle seating here instead of also using the default mount fallback
if context.event then
context.event.cancel()
end
local player = context.event and context.event.player
if not player then return end
-- Check if the player is already sitting on this prop
local passengers = context.prop:get_passengers()
for i = 1, #passengers do
if passengers[i].uuid == player.uuid then
-- Player is already seated, do nothing on right-click
return
end
end
-- Mount the player on the chair
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)
-- Cancel default left-click damage
if context.event then
context.event.cancel()
end
local player = context.event and context.event.player
if not player then return end
-- Check if the player is sitting and dismount them
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 une double montée et appellecontext.prop:mount(player)pour demander le premier siège disponible d'un point de monturem_. -
Clic gauche pour se lever --
on_left_clickannule les dégâts par défaut de ce clic sur le prop, puis vérifie si le joueur qui frappe est actuellement passager. Si c'est le cas,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 qui interagit dans la liste. -
Retour sonore -- Les sons accompagnent les demandes en file d'attente de montée et de descente, pas des changements de siège confirmés. Le hook de clic droit annule la montée par défaut de FMM pour éviter une seconde tentative. Le modèle doit avoir un point
m_libre.
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, gestion du cooldown, et retour par particules/son.
Fichier de script complet (cliquez pour développer)
local COOLDOWN_TICKS = 600 -- 30 seconds between uses
local BLESSINGS = {
{ effect = "speed", name = "Swiftness" },
{ effect = "strength", name = "Strength" },
{ effect = "regeneration", name = "Regeneration" },
{ effect = "resistance", name = "Resistance" },
{ effect = "jump_boost", name = "Leap" },
{ effect = "haste", name = "Haste" },
}
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Prevent default left-click damage
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
-- Check cooldown
if context.state.on_cooldown then
player:send_message("&eThe shrine is recharging... Please wait.")
return
end
-- Check if the player is holding a gold ingot
local held = player:get_held_item()
if not held or held.type ~= "GOLD_INGOT" then
player:send_message("&eThe shrine demands a gold offering...")
return
end
-- Consume one gold ingot
player:consume_held_item(1)
-- Play blessing effects
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
-- Apply a random blessing
local chosen = BLESSINGS[math.random(#BLESSINGS)]
player:add_potion_effect(chosen.effect, 600, 1) -- 30 seconds, level II
player:send_message("&aThe shrine blesses you with " .. chosen.name .. "!")
-- 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 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).held.typeest le nom de matériau Bukkit en majuscules, donc nous le comparons à"GOLD_INGOT". -
Consommation de l'objet --
player:consume_held_item(1)met en attente un retrait de la main principale. La méthode lit cette main à l'exécution de la tâche et ne retourne aucun résultat de paiement. Cette démonstration n'est pas un échange atomique : un changement d'objet tenu peut modifier ce qui est consommé, tandis que la bénédiction est appliquée immédiatement. -
Buff aléatoire -- La table
BLESSINGSà la portée 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 correspond à 30 secondes, l'amplificateur1est le niveau II. -
Temps de recharge -- L'indicateur partagé par cette association de script est réinitialisé après 600 ticks serveur, soit 30 secondes à 20 TPS. Tous les joueurs de ce sanctuaire le partagent. Pour un délai mesuré en temps écoulé, utilisez
context.cooldownscomme dans l'exemple du prop sonore. -
Retour -- Des particules d'enchantement et de villageois content plus un son d'activation de balise créent une sensation de « bénédiction divine ».
Exemple : sanctuaire maudit
Ce que cet exemple enseigne : Effets de potion négatifs, frappes de foudre, apparition d'entités, et logique de branchement basée sur les offrandes du joueur.
Fichier de script complet (cliquez pour développer)
local COOLDOWN_TICKS = 600 -- 30 seconds between uses
local BUFFS = {
{ effect = "speed", name = "Swiftness" },
{ effect = "strength", name = "Strength" },
{ effect = "regeneration", name = "Regeneration" },
{ effect = "resistance", name = "Resistance" },
}
local CURSES = {
{ effect = "slowness", name = "Slowness" },
{ effect = "weakness", name = "Weakness" },
{ effect = "poison", name = "Poison" },
{ effect = "mining_fatigue", name = "Mining Fatigue" },
}
local ZOMBIE_COUNT = 4
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Prevent default left-click damage
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
-- Check cooldown
if context.state.on_cooldown then
player:send_message("&7The dark shrine pulses with residual energy...")
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Check if the player is holding a gold ingot
local held = player:get_held_item()
if not held or held.type ~= "GOLD_INGOT" then
-- No offering -- punish the player!
player:send_message("&4The shrine demands tribute! You dare approach empty-handed?!")
-- Strike lightning on the player
local player_loc = player.current_location
if player_loc then
context.world:strike_lightning(player_loc.x, player_loc.y, player_loc.z)
end
-- Spawn a horde of zombies around the shrine
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
-- Apply a random curse
local chosen_curse = CURSES[math.random(#CURSES)]
player:add_potion_effect(chosen_curse.effect, 400, 1) -- 20 seconds, level II
player:send_message("&cThe shrine curses you with " .. chosen_curse.name .. "!")
-- Ominous effects
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
-- Gold offered -- reward the player
player:consume_held_item(1)
-- Apply a random buff
local chosen_buff = BUFFS[math.random(#BUFFS)]
player:add_potion_effect(chosen_buff.effect, 600, 1) -- 30 seconds, level II
player:send_message("&aThe dark shrine accepts your offering. You are blessed with " .. chosen_buff.name .. "!")
-- Positive feedback
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
-- Set cooldown regardless of path
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 emprunte l'un de deux chemins : punition si le joueur n'a pas d'or, ou récompense s'il en a. -
Frappe de foudre --
context.world:strike_lightning(x, y, z)frappe une vraie foudre (qui inflige 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)crée des zombies ordinaires en cercle autour du sanctuaire. Supprimer le prop ne supprime pas ces créatures. L'exemple ne vérifie ni le terrain ni le nombre total de zombies encore vivants. -
Effets de potion négatifs --
player:add_potion_effect("poison", 400, 1)applique 20 secondes de Poison II. Les noms d'effet sont des chaînes en minuscules correspondant aux nomsPotionEffectTypede Bukkit. -
Récompense -- Le sanctuaire met en attente la consommation de la main principale et applique un effet positif aléatoire. Comme pour le sanctuaire de bénédiction, c'est une démonstration et non un échange atomique entre paiement et récompense.
-
Temps de recharge -- Les deux branches activent le même indicateur pour tous les joueurs utilisant cette association de script. Il est réinitialisé après 600 ticks serveur, soit 30 secondes à 20 TPS.
Faire apparaître plusieurs entités à la fois peut impacter les performances du serveur. Gardez le nombre bas (4-6) et envisagez d'ajouter un cooldown par joueur si de nombreux joueurs utilisent le sanctuaire simultanément.
Exemple : globe tournant
Ce que cet exemple enseigne : Jouer une animation chronométrée à l'interaction, programmer 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 seconds in ticks
return {
api_version = 1,
on_spawn = function(context)
context.state.is_spinning = false
end,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_right_click = function(context)
-- Prevent starting a new spin while already spinning
if context.state.is_spinning then
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Start the spin animation (non-looping)
context.prop:play_animation(SPIN_ANIMATION, true, false)
context.state.is_spinning = true
-- Play a mechanical clicking sound
context.world:play_sound("BLOCK_CHAIN_PLACE", loc.x, loc.y, loc.z, 1.0, 1.5)
-- Schedule the animation to stop after 5 seconds
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 plusieurs demandes de rotation qui se chevauchent. Le flag est défini lorsque la rotation démarre et effacé lorsque l'arrêt programmé se déclenche. -
Animation chronométrée --
play_animation(SPIN_ANIMATION, true, false)demande une animation sans boucle par la file d'attente. L'arrêt est demandé 100 ticks serveur après le clic, pas après le début de lecture. Une file occupée peut raccourcir ou empêcher la rotation visible, et le moteur partagé n'arrête pas une animation de mort active. L'exemple active l'indicateur même si la demande renvoiefalse. -
Sons mécaniques --
BLOCK_CHAIN_PLACEdonne un son de démarrage cliquetant/mécanique ;BLOCK_CHAIN_FALLdonne un son d'arrêt en fin de course. Ajustez la hauteur selon vos goûts. -
Contexte du callback -- Le callback
run_laterutiliselater_context(et noncontext) pour tous les 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 (par ex. "rotate", "turn"), mettez à jour la constante SPIN_ANIMATION en conséquence.
Exemple : prop effrayant
Ce que cet exemple enseigne : Déclencheurs de zone de proximité, effets de frayeur one-shot avec un long cooldown, 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 seconds between scares
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
-- Create a small sphere zone around the prop
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, SCARE_RADIUS)
context.state.zone_handle = handle
-- Watch for players entering the zone. The actual scare logic
-- lives in on_zone_enter below.
context.zones:watch(handle, function() end, nil)
end,
on_zone_enter = function(context)
if context.state.on_cooldown then
return
end
local scare_loc = context.prop.current_location
if scare_loc == nil then return end
-- Play the jumpscare animation
context.prop:stop_animation()
context.prop:play_animation(SCARE_ANIMATION, false, false)
-- Scary sound
context.world:play_sound(
"ENTITY_GHAST_SCREAM",
scare_loc.x, scare_loc.y, scare_loc.z,
1.0, 0.7
)
-- Burst of smoke particles
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
)
-- Set cooldown so it does not trigger again immediately
context.state.on_cooldown = true
context.scheduler:run_later(COOLDOWN_TICKS, function(later_context)
later_context.state.on_cooldown = false
end)
end,
on_left_click = function(context)
-- Prevent default left-click damage
if context.event then
context.event.cancel()
end
end,
on_destroy = function(context)
-- 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 détaillée
-
Zone de proximité --
context.zones:create_sphere()crée une zone fixe de 3 blocs de rayon à la position d'apparition.context.zones:watch(handle, function() end, nil)activeon_zone_enter. Déplacez ou recréez la zone si vous déplacez le prop. -
Effets de frayeur -- Le hook
on_zone_enterjoue une animation"jumpscare", un son de cri de ghast, et fait apparaître des particules de fumée de feu de camp. La combinaison crée un effet soudain et effrayant. -
Temps de recharge -- Tous les joueurs partagent l'indicateur de cette association de script. Il est réinitialisé après 1200 ticks serveur, soit 60 secondes à 20 TPS ; la prochaine entrée dans la zone peut alors déclencher une nouvelle frayeur.
-
Pas de hook de sortie -- Le troisième argument
nildecontext.zones:watch()signifie que nous ne nous soucions pas du moment où 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 retiré, le nettoyage explicite est une bonne pratique.
Pour un meilleur effet de jumpscare, cachez le prop au coin d'un mur ou dans une zone sombre. Le rayon de 3 blocs garantit que le joueur est proche avant que la frayeur 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 seconds between spawns
return {
api_version = 1,
on_spawn = function(context)
context.state.on_cooldown = false
end,
on_left_click = function(context)
-- Prevent default left-click damage
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
-- Prevent spawn spam
if context.state.on_cooldown then
player:send_message("&7The spawner is recharging...")
return
end
local loc = context.prop.current_location
if loc == nil then return end
-- Try to spawn the EliteMobs boss
local boss = context.prop:spawn_elitemobs_boss(BOSS_FILE, loc.x, loc.y + 1, loc.z)
if boss then
-- Success -- play spawn effects
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("&cA goblin warrior emerges!")
else
-- EliteMobs is not installed or the boss file was not found
context.log:warn("Could not spawn boss '" .. BOSS_FILE .. "' -- is EliteMobs installed?")
player:send_message("&7The spawner fizzles... (EliteMobs not available)")
-- Fizzle particles as visual feedback
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
-- 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 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. -
Information sur l'échec --
spawn_elitemobs_boss()renvoienilsi l'intégration ne peut pas retourner un boss vivant, notamment si le plugin ou sa configuration manque, ou si l'apparition échoue. Le message de l'exemple suggère de vérifier EliteMobs ; il n'identifie pas la cause exacte. -
Décalage d'apparition -- La position demandée est un bloc au-dessus du prop. Ce n'est pas une vérification du terrain ou des collisions ; choisissez un espace dégagé adapté au boss.
-
Temps de recharge -- Cette association de script permet une tentative tous les 200 ticks serveur, soit 10 secondes à 20 TPS, partagée par tous les joueurs. Elle ne limite pas le nombre de boss vivants et ne les retire pas avec le prop. Ajoutez des limites de rencontre pour une activité répétable.
-
Distinction visuelle/audio -- Le succès utilise des particules de flamme et un son d'invocation d'evoker pour une apparition dramatique. L'échec utilise de la fumée et un son d'extinction de feu pour un effet de « raté » clair, afin que le joueur sache que quelque chose a mal tourné sans vérifier la console.
Le nom de fichier du boss (par 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.
Effets d'objets
Les exemples de scripts d'objets équipés ont été retirés. Utilisez les définitions d'objets personnalisés et les enchantements personnalisés pour les effets d'objets actuels.
Bonnes pratiques
-
Commencez par un hook minuscule 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 d'aide locales. Déclarez les helpers comme
local function toggle_door(context)au-dessus de la table de retour. 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étées une fois terminées. Chaque
run_repeatingdevrait avoir uncancelcorrespondant danson_destroy. Les tâches non nettoyées gaspillent du CPU. -
Utilisez des contextes de callback de scheduler frais. Les callbacks de scheduler reçoivent un paramètre de contexte frais. Utilisez toujours ce paramètre à l'intérieur du callback, et non le
contextextérieur. -
Gardez
on_game_tickléger. Il s'exécute à chaque tick serveur, 20 fois par seconde à 20 TPS. Limitez le travail coûteux aveccontext.cooldownsou un intervalle du planificateur. -
Choisissez explicitement le traitement des dégâts de clic gauche. Incluez une annulation dans
on_left_clicklorsque les coups ne doivent pas déclencher les dégâts par défaut du prop ; cela ne bloque pas toutes les sources de dégâts. -
Les noms de particules acceptent
FLAMEetflame; l’API normalise la casse avant de chercher la particule Bukkit. Les sons acceptent les noms enum Bukkit sans distinction de casse ou des clés valides commeminecraft:block.note_block.harpet les clés des packs personnalisés. Le client doit disposer du son dans son pack de ressources.
Erreurs courantes de débutant
-
Utiliser le
contextextérieur à l'intérieur d'un callback de scheduler. Le contexte extérieur capture un instantané au moment où le hook s'est exécuté. À l'intérieur des callbacks, utilisez toujours le propre paramètre du callback. -
Oublier d'annuler les tâches répétées. Si vous démarrez un
run_repeatingdanson_spawnmais ne l'annulez jamais, la tâche s'exécute jusqu'à ce que le prop soit retiré. -
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 d'animation ne correspond pas à ce qui est dans le fichier de modèle. Vérifiez le modèle pour les noms exacts. -
Les noms de particules acceptent
FLAMEetflame; l’API normalise la casse avant de chercher la particule Bukkit. Les sons acceptent les noms enum Bukkit sans distinction de casse ou des clés valides commeminecraft:block.note_block.harpet les clés des packs personnalisés. Le client doit disposer du son dans son pack de ressources. -
Oublier
api_version = 1. La table renvoyée doit inclure ce champ, sinon FMM ne chargera pas le script. -
Placer des auxiliaires dans la table renvoyée. Hormis
api_versionet le champ facultatifpriority, la table n'accepte que des noms de hooks pris en charge associés à des fonctions. Définissez les auxiliaires localement en dehors de cette table. FMM exécute les scripts dans l'ordre de leur liste configurée ;priorityne les réordonne pas.
Liste de contrôle qualité
Utilisez cette liste de vérification pour valider un script de prop avant de le déployer :
- Le fichier renvoie exactement une table avec
api_version = 1. - Chaque nom de hook correspond exactement à une entrée dans la liste des hooks de props. Les hooks d'enchantements appartiennent à un moteur distinct.
- Vérifiez à la fois
context.eventetcontext.event.cancelavant d'annuler en dehors d'un hook de clic connu. - Les champs
context.statesont initialisés danson_spawn. - Chaque appel
scheduler:run_repeating(...)a unscheduler:cancel(...)correspondant danson_destroy. - Les callbacks de scheduler utilisent le propre paramètre de contexte du callback, et non le
contextextérieur. - Les hooks
on_game_tickencadrent le travail coûteux derrière une vérification. - Tous les noms de méthode existent dans la référence de l'API des props -- pas d'alias inventés.
- Les noms de particules acceptent
FLAMEetflame; l’API normalise la casse avant de chercher la particule Bukkit. Les sons acceptent les noms enum Bukkit sans distinction de casse ou des clés valides commeminecraft:block.note_block.harpet les clés des packs personnalisés. Le client doit disposer du son dans son pack de ressources. - Le script n'appelle aucune opération bloquante ou de longue durée à l'intérieur d'un hook ou d'un callback.
Conseils pour la génération par IA
Si vous voulez que l'IA génère des scripts de prop de manière fiable, assurez-vous que le prompt inclut :
- Le nom exact du hook -- par ex.
on_right_click, et non « quand le joueur clique sur le prop ». - Les noms d'animation du fichier de modèle -- l'IA ne peut pas les deviner ; fournissez-les.
- Les noms d'enum de son -- par ex.
"BLOCK_NOTE_BLOCK_HARP", et non « son de harpe ». - Les noms d'enum de particule -- par ex.
"FLAME", et non « particules de feu ». - Si les clics gauches doivent infliger les dégâts par défaut -- annulez ce traitement avec
on_left_clicketcontext.event.cancel(). - Utilisez des noms de méthode vérifiés -- consultez d'abord la référence de l'API des props. Si une opération nécessaire n'y figure pas, vérifiez le code actuel du moteur plutôt que d'inventer un alias.
Bon exemple de prompt
Écris un script de prop FMM qui joue l'animation « activate » au clic droit, annule les dégâts de clic gauche par défaut, fait apparaître des particules FLAME à l'emplacement du prop au clic, joue le son BLOCK_LEVER_CLICK et a un temps de recharge de 2 secondes entre les clics avec context.cooldowns:check_local("activate", 40).
Compatibilité des contenus d'objets
Les anciens packs d'objets utilisant scripts: nécessitent des définitions d'objets et du contenu d'enchantements mis à jour. FMM ignore ces définitions retirées et laisse les fichiers inchangés. Ajouter un champ material: n'active plus de script d'objet.