A gramática dos plugins
specVersion 1
Um plugin NimBlock não é código: é um ficheiro JSON que descreve regras, lido e executado por um motor Paper escrito uma vez para todos. Esta página diz exatamente o que podes escrever nele, e mais nada é possível.
Uma regra, e nada mais que uma regra
Toda a gramática cabe numa frase: quando isto acontece, se estas condições se verificam, então fazer aquilo. Um plugin é uma lista de regras, uma regra é um gatilho, zero a 20 condições e uma a 50 ações.
Não há Java gerado em lado nenhum, nem compilação: o servidor carrega um motor que lê a spec. É essa propriedade que conta a seguir, porque uma nova versão do Minecraft paga-se uma vez nesse motor, e não em cada um dos plugins escritos com ele.
A forma de uma spec
{
"specVersion": 1,
"name": "Bem-vindo",
"rules": [
{
"id": "boas-vindas",
"name": "Mensagem de boas-vindas",
"match": "all",
"trigger": { "type": "player.join" },
"conditions": [
{ "type": "player.has_permission", "params": { "permission": "nimblock.vip" } }
],
"actions": [
{ "type": "player.send_message", "params": { "message": "&6Bem-vindo {player}!" } }
]
}
]
}
match vale all (predefinição) ou any, e uma
condição pode ter "not": true. enabled: false mantém
uma regra no ficheiro sem a ativar.
A gramática, em EBNF
As listas de terminais abaixo são geradas a partir do catálogo: são literalmente os identificadores que o motor sabe executar, não uma transcrição feita à mão.
<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"
Há duas coisas que não se leem nestas regras de produção, porque dizem respeito à
coerência e não à forma. Uma condição ou uma ação que exige um jogador só se
escreve sob um gatilho que forneça um: «curar o jogador» não faz sentido sob
«a cada N segundos». E cancel_event só se escreve sob um gatilho
cancelável. Ambas são recusadas na escrita, não na execução.
Os modelos
Os parâmetros do tipo text são modelos:
{player} é substituído pelo que o gatilho forneceu,
{{ dá uma chaveta literal, e os códigos de cor
&a &l são traduzidos.
Uma variável desconhecida do gatilho é um erro, não um texto. Cada gatilho declara o que fornece (ver a lista mais abaixo): fora dessa lista, não há nada para ler, e um erro de escrita é recusado logo na escrita em vez de aterrar no chat dos jogadores. Os parâmetros de um gatilho, esses, não são modelos: são lidos no arranque do servidor, antes de haver seja o que for para substituir.
Os nomes do Minecraft escrevem-se de duas formas consoante o que designam. Um
item nomeia-se em qualquer uma das grafias
(DIAMOND_SWORD ou diamond_sword). Um som
ou um efeito nomeia-se pela sua chave Minecraft
(entity.player.levelup, speed) e não pela constante
Bukkit: a chave segue tal e qual para o cliente, sobrevive às mudanças de versão.
O que a gramática não permite
É fechada, e isso diz-se antes do resto. O que consta no catálogo é possível, o resto é impossível, e assim continuará: não há forma nenhuma de executar Java arbitrário. É isto que torna o estúdio instantâneo (nada para compilar) e uma spec inofensiva de instalar, seja qual for a sua origem.
As condições não se aninham: é um limite assumido da versão 1,
uma árvore booleana desenha-se mal em blocos, e all / any
/ not quase sempre chegam. A única saída é a ação «executar um
comando como consola», que nesse servidor tem exatamente o poder da consola,
nem mais nem menos.
Os gatilhos
O que desencadeia uma regra, e as variáveis que cada um fornece para os modelos.
player.join
Um jogador entra
No momento em que o jogador entra no servidor, depois de o mundo dele estar carregado.
fornece:
{player} O jogador que entra,
{world} O mundo onde ele chega
player.quit
Um jogador sai
No momento em que o jogador sai do servidor. Continua contactável, mas qualquer mensagem enviada perde-se.
fornece:
{player} O jogador que sai,
{world} O mundo que ele deixa
player.chat
Um jogador escreve no chat
cancelável
Antes de a mensagem ser distribuída aos outros jogadores. Anulável: nesse caso, ninguém vê a mensagem.
fornece:
{player} O autor da mensagem,
{world} O mundo dele,
{message} A mensagem escrita
player.death
Um jogador morre
Na morte do jogador, antes do ecrã de reaparecimento.
fornece:
{player} O jogador morto,
{world} O mundo dele,
{killer} O jogador responsável, vazio se não houver nenhum
player.respawn
Um jogador reaparece
Quando o jogador volta ao jogo depois de morrer.
fornece:
{player} O jogador que reaparece,
{world} O mundo de reaparecimento
block.break
Um jogador parte um bloco
cancelável
Antes de o bloco desaparecer. Anulável: o bloco continua no lugar.
fornece:
{player} O jogador que parte,
{world} O mundo dele,
{block} O tipo de bloco (DIAMOND_ORE),
{x} Coordenada X do bloco,
{y} Altura do bloco,
{z} Coordenada Z do bloco
block.place
Um jogador coloca um bloco
cancelável
Antes de o bloco ser colocado. Anulável: o bloco não é colocado.
fornece:
{player} O jogador que coloca,
{world} O mundo dele,
{block} O tipo de bloco colocado,
{x} Coordenada X do bloco,
{y} Altura do bloco,
{z} Coordenada Z do bloco
command
Um jogador escreve um comando
Cria um novo comando no servidor. ⚠️ Um comando cujo nome ainda não existia só aparece depois de reiniciar o servidor: o Paper só aceita novos nomes de comandos no arranque. O conteúdo da regra, esse, recarrega sem reiniciar.
-
nametext Nome do comando, sem a barra -
descriptiontext facultativo Descrição apresentada na ajuda -
permissiontext facultativo Permissão exigida, vazio para todos
fornece:
{player} O jogador que escreveu o comando,
{world} O mundo dele,
{args} O que se segue ao comando, tal como escrito
schedule.repeat
A cada N segundos
Repete a regra enquanto o servidor estiver a funcionar. Nenhum jogador está envolvido: só se podem usar ações que não precisem de nenhum.
-
secondsinteger [1..86400] Intervalo em segundos
As condições
O que filtra. Zero a 20 por regra, combinadas por match.
player.has_permission
O jogador tem a permissão
precisa de um jogador
-
permissiontext Permissão
player.is_op
O jogador é operador
precisa de um jogador
player.in_world
O jogador está no mundo
precisa de um jogador
-
worldtext Nome do mundo
player.health_below
O jogador tem menos de X corações
precisa de um jogador
Em pontos de vida: 20 pontos equivalem a 10 corações.
-
valuenumber [0..1024] Pontos de vida
text.contains
Um texto contém
-
texttext Texto examinado -
searchtext O que procurar nele -
ignoreCaseboolean facultativo, predefinição:trueIgnorar maiúsculas e minúsculas
text.equals
Um texto é igual a
-
texttext Texto examinado -
valuetext Valor esperado -
ignoreCaseboolean facultativo, predefinição:trueIgnorar maiúsculas e minúsculas
number.compare
Comparar dois números
Os dois lados são modelos: {y} compara-se com 62 sem escrever código.
-
lefttext Lado esquerdo -
operator< | <= | == | != | >= | > Comparação -
righttext Lado direito
chance
Ao acaso, X % das vezes
-
percentnumber [0..100] Percentagem
As ações
O que acontece. Uma a 50 por regra, executadas por ordem.
player.send_message
Enviar uma mensagem ao jogador
precisa de um jogador
-
messagetext Mensagem
player.send_actionbar
Mostrar uma mensagem acima da barra de ação
precisa de um jogador
-
messagetext Mensagem
player.send_title
Mostrar um título em grande
precisa de um jogador
-
titletext Título -
subtitletext facultativo Subtítulo -
fadeInnumber [0..60] facultativo, predefinição:0.5Aparecimento em segundos -
staynumber [0..600] facultativo, predefinição:3Duração em segundos -
fadeOutnumber [0..60] facultativo, predefinição:0.5Desaparecimento em segundos
player.play_sound
Tocar um som para o jogador
precisa de um jogador
-
soundidentifier Som, em chave Minecraft (entity.player.levelup) -
volumenumber [0..10] facultativo, predefinição:1Volume -
pitchnumber [0.5..2] facultativo, predefinição:1Tom
player.give_item
Dar um item ao jogador
precisa de um jogador
O que não couber no inventário cai no chão, aos pés dele.
-
materialidentifier Item (DIAMOND_SWORD) -
amountinteger [1..2304] facultativo, predefinição:1Quantidade
player.give_effect
Aplicar um efeito ao jogador
precisa de um jogador
-
effectidentifier Efeito, em chave Minecraft (night_vision) -
secondsinteger [1..86400] facultativo, predefinição:10Duração em segundos -
amplifierinteger [0..255] facultativo, predefinição:0Nível, a partir de 0
player.heal
Curar o jogador
precisa de um jogador
Vida ao máximo, fome saciada.
player.teleport
Teletransportar o jogador
precisa de um jogador
-
xnumber [-30000000..30000000] Coordenada X -
ynumber [-512..1024] Altura -
znumber [-30000000..30000000] Coordenada Z -
worldtext facultativo Mundo, vazio para o do jogador
player.kick
Expulsar o jogador
precisa de um jogador
-
reasontext Motivo apresentado
broadcast
Anunciar a todo o servidor
-
messagetext Mensagem
server.run_command
Executar um comando como consola
A válvula de escape da gramática: tudo o que a consola sabe fazer. Ou seja, o poder da consola sobre este servidor, por isso só se deve escrever com conhecimento de causa.
-
commandtext Comando, sem a barra
cancel_event
Anular o que acabou de acontecer
precisa de um acionador cancelável
Só existe em gatilhos anuláveis: a mensagem não é distribuída, o bloco não é partido.
log
Escrever na consola do servidor
-
messagetext Mensagem -
levelinfo | warn facultativo, predefinição:infoNível
delay
Esperar
Coloca em pausa as restantes ações da regra. Uma regra que espera já não pode anular nada: "Anular" tem de vir antes.
-
secondsnumber [0.05..3600] Segundos
Os ficheiros publicados
O JSON Schema é gerado a partir do catálogo, a cada implementação, e é a referência legível por uma máquina: um editor de texto que o segue completa os identificadores e recusa parâmetros desconhecidos.
- plugin-1.schema.json : o esquema, em JSON Schema draft 2020-12.
- catalogue-1.json : o próprio catálogo, a fonte de onde tudo o resto deriva (o validador, o esquema, os blocos do editor e o motor Java).
O catálogo tem as etiquetas em francês, que é a língua de origem. Os identificadores, esses, não se traduzem: são eles que o motor executa.
O que acontece quando a gramática muda
Adicionar um gatilho, uma condição ou uma ação não parte nada: uma spec escrita
antes continua a funcionar. Uma alteração de rutura, essa,
incrementa specVersion, e o motor recusa então o que não entende em
vez de o executar de forma errada, dizendo-o na consola do servidor.
As URLs acima têm esse número: plugin-1.schema.json
designará sempre esta gramática, mesmo no dia em que exista outra.