{
    "$comment": "Grammaire fermée des plugins NimBlock. Source unique du panel PHP, du runtime Java et du schéma JSON généré. Voir spec/README.md.",
    "specVersion": 1,
    "paramTypes": {
        "text": "Chaîne de caractères. Gabarit : {variable} est remplacé par le contexte du déclencheur, {{ donne une accolade littérale, et les codes couleur &a &l sont traduits.",
        "identifier": "Nom Minecraft. Les objets s'écrivent DIAMOND_SWORD ou diamond_sword ; les sons et les effets s'écrivent en clé (entity.player.levelup, speed), forme qui part telle quelle au client et survit aux versions. Le registre visé est donné par « registry » ; l'existence n'est vérifiable qu'à l'exécution.",
        "integer": "Entier.",
        "number": "Nombre décimal.",
        "boolean": "Vrai ou faux.",
        "choice": "Une valeur parmi « choices »."
    },
    "contextTypes": {
        "player": "Le joueur concerné. Interpolé par son pseudo.",
        "text": "Texte libre.",
        "number": "Nombre."
    },
    "triggers": [
        {
            "id": "player.join",
            "label": "Un joueur se connecte",
            "description": "Au moment où le joueur entre sur le serveur, après le chargement de son monde.",
            "cancellable": false,
            "params": [],
            "context": [
                { "name": "player", "type": "player", "label": "Le joueur qui se connecte" },
                { "name": "world", "type": "text", "label": "Le monde où il arrive" }
            ]
        },
        {
            "id": "player.quit",
            "label": "Un joueur se déconnecte",
            "description": "Au moment où le joueur quitte le serveur. Il est encore joignable, mais tout envoi de message est perdu.",
            "cancellable": false,
            "params": [],
            "context": [
                { "name": "player", "type": "player", "label": "Le joueur qui part" },
                { "name": "world", "type": "text", "label": "Le monde qu'il quitte" }
            ]
        },
        {
            "id": "player.chat",
            "label": "Un joueur écrit dans le chat",
            "description": "Avant que le message ne soit distribué aux autres joueurs. Annulable : le message n'est alors vu de personne.",
            "cancellable": true,
            "params": [],
            "context": [
                { "name": "player", "type": "player", "label": "L'auteur du message" },
                { "name": "world", "type": "text", "label": "Son monde" },
                { "name": "message", "type": "text", "label": "Le message écrit" }
            ]
        },
        {
            "id": "player.death",
            "label": "Un joueur meurt",
            "description": "À la mort du joueur, avant l'écran de réapparition.",
            "cancellable": false,
            "params": [],
            "context": [
                { "name": "player", "type": "player", "label": "Le joueur mort" },
                { "name": "world", "type": "text", "label": "Son monde" },
                { "name": "killer", "type": "text", "label": "Le joueur responsable, vide s'il n'y en a pas" }
            ]
        },
        {
            "id": "player.respawn",
            "label": "Un joueur réapparaît",
            "description": "Quand le joueur revient en jeu après sa mort.",
            "cancellable": false,
            "params": [],
            "context": [
                { "name": "player", "type": "player", "label": "Le joueur qui réapparaît" },
                { "name": "world", "type": "text", "label": "Le monde de réapparition" }
            ]
        },
        {
            "id": "block.break",
            "label": "Un joueur casse un bloc",
            "description": "Avant que le bloc ne disparaisse. Annulable : le bloc reste en place.",
            "cancellable": true,
            "params": [],
            "context": [
                { "name": "player", "type": "player", "label": "Le joueur qui casse" },
                { "name": "world", "type": "text", "label": "Son monde" },
                { "name": "block", "type": "text", "label": "Le type de bloc (DIAMOND_ORE)" },
                { "name": "x", "type": "number", "label": "Abscisse du bloc" },
                { "name": "y", "type": "number", "label": "Hauteur du bloc" },
                { "name": "z", "type": "number", "label": "Ordonnée du bloc" }
            ]
        },
        {
            "id": "block.place",
            "label": "Un joueur pose un bloc",
            "description": "Avant que le bloc ne soit posé. Annulable : le bloc n'est pas posé.",
            "cancellable": true,
            "params": [],
            "context": [
                { "name": "player", "type": "player", "label": "Le joueur qui pose" },
                { "name": "world", "type": "text", "label": "Son monde" },
                { "name": "block", "type": "text", "label": "Le type de bloc posé" },
                { "name": "x", "type": "number", "label": "Abscisse du bloc" },
                { "name": "y", "type": "number", "label": "Hauteur du bloc" },
                { "name": "z", "type": "number", "label": "Ordonnée du bloc" }
            ]
        },
        {
            "id": "command",
            "label": "Un joueur tape une commande",
            "description": "Crée une nouvelle commande sur le serveur. ⚠️ Une commande dont le nom n'existait pas encore n'apparaît qu'au redémarrage du serveur : Paper n'accepte de nouveaux noms de commandes qu'à son démarrage. Le contenu de la règle, lui, se recharge à chaud.",
            "cancellable": false,
            "params": [
                { "name": "name", "type": "text", "label": "Nom de la commande, sans le slash", "required": true, "pattern": "^[a-z][a-z0-9_-]{0,31}$" },
                { "name": "description", "type": "text", "label": "Description affichée dans l'aide", "required": false, "default": "" },
                { "name": "permission", "type": "text", "label": "Permission exigée, vide pour tout le monde", "required": false, "default": "" }
            ],
            "context": [
                { "name": "player", "type": "player", "label": "Le joueur qui a tapé la commande" },
                { "name": "world", "type": "text", "label": "Son monde" },
                { "name": "args", "type": "text", "label": "Ce qui suit la commande, tel quel" }
            ]
        },
        {
            "id": "schedule.repeat",
            "label": "Toutes les N secondes",
            "description": "Répète la règle tant que le serveur tourne. Aucun joueur n'est concerné : seules les actions qui n'en demandent pas sont utilisables.",
            "cancellable": false,
            "params": [
                { "name": "seconds", "type": "integer", "label": "Intervalle en secondes", "required": true, "min": 1, "max": 86400 }
            ],
            "context": []
        }
    ],
    "conditions": [
        {
            "id": "player.has_permission",
            "label": "Le joueur a la permission",
            "requires": ["player"],
            "params": [
                { "name": "permission", "type": "text", "label": "Permission", "required": true }
            ]
        },
        {
            "id": "player.is_op",
            "label": "Le joueur est opérateur",
            "requires": ["player"],
            "params": []
        },
        {
            "id": "player.in_world",
            "label": "Le joueur est dans le monde",
            "requires": ["player"],
            "params": [
                { "name": "world", "type": "text", "label": "Nom du monde", "required": true }
            ]
        },
        {
            "id": "player.health_below",
            "label": "Le joueur a moins de X cœurs",
            "description": "En points de vie : 20 points valent 10 cœurs.",
            "requires": ["player"],
            "params": [
                { "name": "value", "type": "number", "label": "Points de vie", "required": true, "min": 0, "max": 1024 }
            ]
        },
        {
            "id": "text.contains",
            "label": "Un texte contient",
            "requires": [],
            "params": [
                { "name": "text", "type": "text", "label": "Texte examiné", "required": true },
                { "name": "search", "type": "text", "label": "Ce qu'on y cherche", "required": true },
                { "name": "ignoreCase", "type": "boolean", "label": "Ignorer la casse", "required": false, "default": true }
            ]
        },
        {
            "id": "text.equals",
            "label": "Un texte est égal à",
            "requires": [],
            "params": [
                { "name": "text", "type": "text", "label": "Texte examiné", "required": true },
                { "name": "value", "type": "text", "label": "Valeur attendue", "required": true },
                { "name": "ignoreCase", "type": "boolean", "label": "Ignorer la casse", "required": false, "default": true }
            ]
        },
        {
            "id": "number.compare",
            "label": "Comparer deux nombres",
            "description": "Les deux membres sont des gabarits : {y} se compare à 62 sans écrire de code.",
            "requires": [],
            "params": [
                { "name": "left", "type": "text", "label": "Membre de gauche", "required": true },
                { "name": "operator", "type": "choice", "label": "Comparaison", "required": true, "choices": ["<", "<=", "==", "!=", ">=", ">"] },
                { "name": "right", "type": "text", "label": "Membre de droite", "required": true }
            ]
        },
        {
            "id": "chance",
            "label": "Au hasard, X % du temps",
            "requires": [],
            "params": [
                { "name": "percent", "type": "number", "label": "Pourcentage", "required": true, "min": 0, "max": 100 }
            ]
        }
    ],
    "actions": [
        {
            "id": "player.send_message",
            "label": "Envoyer un message au joueur",
            "requires": ["player"],
            "params": [
                { "name": "message", "type": "text", "label": "Message", "required": true }
            ]
        },
        {
            "id": "player.send_actionbar",
            "label": "Afficher un message au-dessus de la barre d'action",
            "requires": ["player"],
            "params": [
                { "name": "message", "type": "text", "label": "Message", "required": true }
            ]
        },
        {
            "id": "player.send_title",
            "label": "Afficher un titre en grand",
            "requires": ["player"],
            "params": [
                { "name": "title", "type": "text", "label": "Titre", "required": true },
                { "name": "subtitle", "type": "text", "label": "Sous-titre", "required": false, "default": "" },
                { "name": "fadeIn", "type": "number", "label": "Apparition en secondes", "required": false, "default": 0.5, "min": 0, "max": 60 },
                { "name": "stay", "type": "number", "label": "Durée en secondes", "required": false, "default": 3, "min": 0, "max": 600 },
                { "name": "fadeOut", "type": "number", "label": "Disparition en secondes", "required": false, "default": 0.5, "min": 0, "max": 60 }
            ]
        },
        {
            "id": "player.play_sound",
            "label": "Jouer un son pour le joueur",
            "requires": ["player"],
            "params": [
                { "name": "sound", "type": "identifier", "label": "Son, en clé Minecraft (entity.player.levelup)", "registry": "sound", "required": true },
                { "name": "volume", "type": "number", "label": "Volume", "required": false, "default": 1, "min": 0, "max": 10 },
                { "name": "pitch", "type": "number", "label": "Hauteur", "required": false, "default": 1, "min": 0.5, "max": 2 }
            ]
        },
        {
            "id": "player.give_item",
            "label": "Donner un objet au joueur",
            "description": "Ce qui ne tient pas dans l'inventaire tombe au sol à ses pieds.",
            "requires": ["player"],
            "params": [
                { "name": "material", "type": "identifier", "label": "Objet (DIAMOND_SWORD)", "registry": "material", "required": true },
                { "name": "amount", "type": "integer", "label": "Quantité", "required": false, "default": 1, "min": 1, "max": 2304 }
            ]
        },
        {
            "id": "player.give_effect",
            "label": "Appliquer un effet au joueur",
            "requires": ["player"],
            "params": [
                { "name": "effect", "type": "identifier", "label": "Effet, en clé Minecraft (night_vision)", "registry": "effect", "required": true },
                { "name": "seconds", "type": "integer", "label": "Durée en secondes", "required": false, "default": 10, "min": 1, "max": 86400 },
                { "name": "amplifier", "type": "integer", "label": "Niveau, à partir de 0", "required": false, "default": 0, "min": 0, "max": 255 }
            ]
        },
        {
            "id": "player.heal",
            "label": "Soigner le joueur",
            "description": "Vie au maximum, faim rassasiée.",
            "requires": ["player"],
            "params": []
        },
        {
            "id": "player.teleport",
            "label": "Téléporter le joueur",
            "requires": ["player"],
            "params": [
                { "name": "x", "type": "number", "label": "Abscisse", "required": true, "min": -30000000, "max": 30000000 },
                { "name": "y", "type": "number", "label": "Hauteur", "required": true, "min": -512, "max": 1024 },
                { "name": "z", "type": "number", "label": "Ordonnée", "required": true, "min": -30000000, "max": 30000000 },
                { "name": "world", "type": "text", "label": "Monde, vide pour celui du joueur", "required": false, "default": "" }
            ]
        },
        {
            "id": "player.kick",
            "label": "Expulser le joueur",
            "requires": ["player"],
            "params": [
                { "name": "reason", "type": "text", "label": "Motif affiché", "required": true }
            ]
        },
        {
            "id": "broadcast",
            "label": "Annoncer à tout le serveur",
            "requires": [],
            "params": [
                { "name": "message", "type": "text", "label": "Message", "required": true }
            ]
        },
        {
            "id": "server.run_command",
            "label": "Exécuter une commande en tant que console",
            "description": "La porte de sortie de la grammaire : tout ce que la console sait faire. Le pouvoir de la console sur ce serveur, donc à n'écrire qu'en connaissance de cause.",
            "requires": [],
            "params": [
                { "name": "command", "type": "text", "label": "Commande, sans le slash", "required": true }
            ]
        },
        {
            "id": "cancel_event",
            "label": "Annuler ce qui vient de se passer",
            "description": "N'existe que sur les déclencheurs annulables : le message n'est pas distribué, le bloc n'est pas cassé.",
            "requires": ["cancellable"],
            "params": []
        },
        {
            "id": "log",
            "label": "Écrire dans la console du serveur",
            "requires": [],
            "params": [
                { "name": "message", "type": "text", "label": "Message", "required": true },
                { "name": "level", "type": "choice", "label": "Niveau", "required": false, "default": "info", "choices": ["info", "warn"] }
            ]
        },
        {
            "id": "delay",
            "label": "Attendre",
            "description": "Met en pause la suite des actions de la règle. Une règle qui attend ne peut plus rien annuler : « Annuler » doit venir avant.",
            "requires": [],
            "params": [
                { "name": "seconds", "type": "number", "label": "Secondes", "required": true, "min": 0.05, "max": 3600 }
            ]
        }
    ]
}
