NimBlock Accedi

La grammatica dei plugin

specVersion 1

Un plugin NimBlock non è codice: è un file JSON che descrive delle regole, letto ed eseguito da un motore Paper scritto una volta per tutti. Questa pagina dice esattamente cosa puoi scriverci, e nient'altro è possibile.

Una regola, e nient'altro che una regola

Tutta la grammatica sta in una frase: quando succede questo, se queste condizioni sono vere, allora fai questo. Un plugin è un elenco di regole, una regola è un trigger, da zero a 20 condizioni e da una a 50 azioni.

Non c'è Java generato da nessuna parte, né alcuna compilazione: il server carica un motore che legge la spec. È questa la proprietà che conta per il seguito, perché una nuova versione di Minecraft si paga una volta sola in questo motore, e non in ognuno dei plugin scritti con esso.

La forma di una spec

{
    "specVersion": 1,
    "name": "Benvenuto",
    "rules": [
        {
            "id": "benvenuto",
            "name": "Messaggio di benvenuto",
            "match": "all",
            "trigger": { "type": "player.join" },
            "conditions": [
                { "type": "player.has_permission", "params": { "permission": "nimblock.vip" } }
            ],
            "actions": [
                { "type": "player.send_message", "params": { "message": "&6Benvenuto {player}!" } }
            ]
        }
    ]
}

match vale all (predefinito) oppure any, e una condizione può avere "not": true. enabled: false mantiene una regola nel file senza attivarla.

La grammatica, in EBNF

Gli elenchi di terminali qui sotto sono generati dal catalogo: sono letteralmente gli identificatori che il motore sa eseguire, non una trascrizione fatta a mano.

<plugin>       ::= "{" "specVersion" ":" 1 ","
                       [ "id" ":" <slug64> "," ]
                       "name" ":" <label> ","
                       [ "description" ":" <text500> "," ]
                       "rules" ":" "[" <rule> { "," <rule> } "]" "}"      (* 1..200 *)

<rule>         ::= "{" [ "id" ":" <slug32> "," ]
                       "name" ":" <label> ","
                       [ "enabled" ":" <boolean> "," ]                   (* = true *)
                       [ "match" ":" ( "all" | "any" ) "," ]              (* = "all" *)
                       "trigger" ":" <trigger> ","
                       [ "conditions" ":" "[" [ <condition>
                             { "," <condition> } ] "]" "," ]              (* 0..20 *)
                       "actions" ":" "[" <action>
                             { "," <action> } "]" "}"                     (* 1..50 *)

<trigger>      ::= "{" "type" ":" <trigger-id>   [ "," "params" ":" <params> ] "}"
<condition>    ::= "{" "type" ":" <condition-id> [ "," "not" ":" <boolean> ]
                                                 [ "," "params" ":" <params> ] "}"
<action>       ::= "{" "type" ":" <action-id>    [ "," "params" ":" <params> ] "}"

<params>       ::= "{" [ <param-name> ":" <param-value>
                       { "," <param-name> ":" <param-value> } ] "}"
<param-value>  ::= <template> | <identifier> | <integer> | <number>
                 | <boolean> | <choice>

<template>     ::= { <literal> | <variable> | <colour> }                (* 0..2000 *)
<literal>      ::= /[^{}]/ | "{{" | "}}"
<variable>     ::= "{" /[a-zA-Z][a-zA-Z0-9_]*/ "}"
<colour>       ::= "&" ( "0".."9" | "a".."f" | "k".."o" | "r" )

<identifier>   ::= /[A-Za-z][A-Za-z0-9_:.\/-]{0,63}/
<slug32>       ::= /[A-Za-z0-9_-]{1,32}/
<slug64>       ::= /.{0,64}/
<label>        ::= /.{1,64}/
<text500>      ::= /.{0,500}/

<trigger-id>   ::= "player.join"
                 | "player.quit"
                 | "player.chat"
                 | "player.death"
                 | "player.respawn"
                 | "block.break"
                 | "block.place"
                 | "command"
                 | "schedule.repeat"
<condition-id> ::= "player.has_permission"
                 | "player.is_op"
                 | "player.in_world"
                 | "player.health_below"
                 | "text.contains"
                 | "text.equals"
                 | "number.compare"
                 | "chance"
<action-id>    ::= "player.send_message"
                 | "player.send_actionbar"
                 | "player.send_title"
                 | "player.play_sound"
                 | "player.give_item"
                 | "player.give_effect"
                 | "player.heal"
                 | "player.teleport"
                 | "player.kick"
                 | "broadcast"
                 | "server.run_command"
                 | "cancel_event"
                 | "log"
                 | "delay"

Due cose non si leggono in queste regole di produzione, perché riguardano la coerenza e non la forma. Una condizione o un'azione che richiede un giocatore si scrive solo sotto un trigger che ne fornisce uno: «curare il giocatore» non ha senso sotto «ogni N secondi». E cancel_event si scrive solo sotto un trigger annullabile. Entrambe sono rifiutate in fase di scrittura, non di esecuzione.

I template

I parametri di tipo text sono template: {player} viene sostituito con ciò che il trigger ha fornito, {{ dà una graffa letterale, e i codici colore &a &l vengono tradotti.

Una variabile sconosciuta al trigger è un errore, non un testo. Ogni trigger dichiara cosa fornisce (vedi l'elenco più sotto): fuori da questo elenco non c'è nulla da leggere, e un errore di battitura viene rifiutato in fase di scrittura invece di finire nella chat dei giocatori. I parametri di un trigger, invece, non sono template: vengono letti all'avvio del server, prima che ci sia qualcosa da sostituire.

I nomi Minecraft si scrivono in due modi a seconda di cosa designano. Un oggetto si scrive in entrambe le grafie (DIAMOND_SWORD oppure diamond_sword). Un suono o un effetto si scrive con la sua chiave Minecraft (entity.player.levelup, speed) e non con la costante Bukkit: la chiave parte così com'è verso il client, e sopravvive ai cambi di versione.

Ciò che la grammatica non permette

È chiusa, e va detto prima di tutto il resto. Ciò che compare nel catalogo è fattibile, il resto è impossibile e lo resterà: non c'è alcun modo di eseguire Java arbitrario. È questo che rende lo studio istantaneo (niente da compilare) e una spec innocua da installare, qualunque sia la sua provenienza.

Le condizioni non si annidano: è un limite assunto consapevolmente della versione 1, un albero booleano si disegna male a blocchi, e all / any / not bastano quasi sempre. L'unica via d'uscita è l'azione «esegui un comando come console», che su quel server ha esattamente il potere della console, né più né meno.

I trigger

Cosa fa scattare una regola, e le variabili che ciascuno fornisce ai template.

player.join Un giocatore si connette

Nel momento in cui il giocatore entra nel server, dopo il caricamento del suo mondo.

fornisce: {player} Il giocatore che si connette, {world} Il mondo in cui arriva

player.quit Un giocatore si disconnette

Nel momento in cui il giocatore lascia il server. È ancora raggiungibile, ma ogni messaggio inviato va perso.

fornisce: {player} Il giocatore che se ne va, {world} Il mondo che lascia

player.chat Un giocatore scrive in chat annullabile

Prima che il messaggio venga distribuito agli altri giocatori. Annullabile: il messaggio non viene quindi visto da nessuno.

fornisce: {player} L'autore del messaggio, {world} Il suo mondo, {message} Il messaggio scritto

player.death Un giocatore muore

Alla morte del giocatore, prima della schermata di ricomparsa.

fornisce: {player} Il giocatore morto, {world} Il suo mondo, {killer} Il giocatore responsabile, vuoto se non ce n'è uno

player.respawn Un giocatore ricompare

Quando il giocatore torna in gioco dopo la sua morte.

fornisce: {player} Il giocatore che ricompare, {world} Il mondo di ricomparsa

block.break Un giocatore distrugge un blocco annullabile

Prima che il blocco scompaia. Annullabile: il blocco resta al suo posto.

fornisce: {player} Il giocatore che distrugge, {world} Il suo mondo, {block} Il tipo di blocco (DIAMOND_ORE), {x} Coordinata X del blocco, {y} Altezza del blocco, {z} Coordinata Z del blocco

block.place Un giocatore piazza un blocco annullabile

Prima che il blocco venga piazzato. Annullabile: il blocco non viene piazzato.

fornisce: {player} Il giocatore che piazza, {world} Il suo mondo, {block} Il tipo di blocco piazzato, {x} Coordinata X del blocco, {y} Altezza del blocco, {z} Coordinata Z del blocco

command Un giocatore digita un comando

Crea un nuovo comando sul server. ⚠️ Un comando il cui nome non esisteva ancora appare solo al riavvio del server: Paper accetta nuovi nomi di comandi solo all'avvio. Il contenuto della regola, invece, si ricarica a caldo.

  • name text Nome del comando, senza la barra
  • description text facoltativo Descrizione mostrata nell'aiuto
  • permission text facoltativo Permesso richiesto, vuoto per tutti

fornisce: {player} Il giocatore che ha digitato il comando, {world} Il suo mondo, {args} Ciò che segue il comando, così com'è

schedule.repeat Ogni N secondi

Ripete la regola finché il server è in funzione. Nessun giocatore è coinvolto: sono utilizzabili solo le azioni che non ne richiedono uno.

  • seconds integer [1..86400] Intervallo in secondi

Le condizioni

Ciò che filtra. Da zero a 20 per regola, combinate tramite match.

player.has_permission Il giocatore ha il permesso richiede un giocatore
  • permission text Permesso
player.is_op Il giocatore è operatore richiede un giocatore
player.in_world Il giocatore è nel mondo richiede un giocatore
  • world text Nome del mondo
player.health_below Il giocatore ha meno di X cuori richiede un giocatore

In punti vita: 20 punti equivalgono a 10 cuori.

  • value number [0..1024] Punti vita
text.contains Un testo contiene
  • text text Testo esaminato
  • search text Cosa cercare
  • ignoreCase boolean facoltativo, predefinito: true Ignora maiuscole e minuscole
text.equals Un testo è uguale a
  • text text Testo esaminato
  • value text Valore atteso
  • ignoreCase boolean facoltativo, predefinito: true Ignora maiuscole e minuscole
number.compare Confronta due numeri

Entrambi i membri sono modelli: {y} si confronta con 62 senza scrivere codice.

  • left text Membro sinistro
  • operator < | <= | == | != | >= | > Confronto
  • right text Membro destro
chance A caso, X % delle volte
  • percent number [0..100] Percentuale

Le azioni

Ciò che accade. Da una a 50 per regola, eseguite in ordine.

player.send_message Invia un messaggio al giocatore richiede un giocatore
  • message text Messaggio
player.send_actionbar Mostra un messaggio sopra la barra delle azioni richiede un giocatore
  • message text Messaggio
player.send_title Mostra un titolo in grande richiede un giocatore
  • title text Titolo
  • subtitle text facoltativo Sottotitolo
  • fadeIn number [0..60] facoltativo, predefinito: 0.5 Comparsa in secondi
  • stay number [0..600] facoltativo, predefinito: 3 Durata in secondi
  • fadeOut number [0..60] facoltativo, predefinito: 0.5 Scomparsa in secondi
player.play_sound Riproduci un suono per il giocatore richiede un giocatore
  • sound identifier Suono, come chiave Minecraft (entity.player.levelup)
  • volume number [0..10] facoltativo, predefinito: 1 Volume
  • pitch number [0.5..2] facoltativo, predefinito: 1 Tono
player.give_item Dai un oggetto al giocatore richiede un giocatore

Quello che non entra nell'inventario cade a terra ai suoi piedi.

  • material identifier Oggetto (DIAMOND_SWORD)
  • amount integer [1..2304] facoltativo, predefinito: 1 Quantità
player.give_effect Applica un effetto al giocatore richiede un giocatore
  • effect identifier Effetto, come chiave Minecraft (night_vision)
  • seconds integer [1..86400] facoltativo, predefinito: 10 Durata in secondi
  • amplifier integer [0..255] facoltativo, predefinito: 0 Livello, a partire da 0
player.heal Cura il giocatore richiede un giocatore

Vita al massimo, fame saziata.

player.teleport Teletrasporta il giocatore richiede un giocatore
  • x number [-30000000..30000000] Coordinata X
  • y number [-512..1024] Altezza
  • z number [-30000000..30000000] Coordinata Z
  • world text facoltativo Mondo, vuoto per quello del giocatore
player.kick Espelli il giocatore richiede un giocatore
  • reason text Motivo mostrato
broadcast Annuncia a tutto il server
  • message text Messaggio
server.run_command Esegui un comando come console

La via d'uscita della grammatica: tutto ciò che la console sa fare. È il pieno potere della console su questo server, quindi scrivilo sapendo cosa stai facendo.

  • command text Comando, senza la barra
cancel_event Annulla ciò che è appena successo richiede un trigger annullabile

Esiste solo sui trigger annullabili: il messaggio non viene distribuito, il blocco non viene distrutto.

log Scrivi nella console del server
  • message text Messaggio
  • level info | warn facoltativo, predefinito: info Livello
delay Attendi

Mette in pausa il resto delle azioni della regola. Una regola che attende non può più annullare nulla: "Annulla" deve venire prima.

  • seconds number [0.05..3600] Secondi

I file pubblicati

Il JSON Schema viene generato dal catalogo a ogni deploy, ed è il riferimento leggibile da una macchina: un editor di testo che lo segue completa gli identificatori e rifiuta i parametri sconosciuti.

  • plugin-1.schema.json : lo schema, in JSON Schema draft 2020-12.
  • catalogue-1.json : il catalogo stesso, la fonte da cui deriva tutto il resto (il validatore, lo schema, i blocchi dell'editor e il motore Java).

Il catalogo porta le etichette in francese, che è la lingua sorgente. Gli identificatori, invece, non si traducono: sono loro che il motore esegue.

Cosa succede quando la grammatica cambia

Aggiungere un trigger, una condizione o un'azione non rompe nulla: una spec scritta prima continua a funzionare. Una modifica incompatibile, invece, incrementa specVersion, e il motore rifiuta allora ciò che non capisce invece di eseguirlo in modo sbagliato, dicendolo nella console del server.

Gli URL qui sopra portano questo numero: plugin-1.schema.json designerà sempre questa grammatica, anche il giorno in cui ne esisterà un'altra.