NimBlock Inloggen

De plugin-grammatica

specVersion 1

Een NimBlock-plugin is geen code: het is een JSON-bestand dat regels beschrijft, gelezen en uitgevoerd door een Paper-engine die één keer voor iedereen is geschreven. Deze pagina zegt precies wat je erin mag schrijven, en niets anders is mogelijk.

Eén regel, en niets dan een regel

De hele grammatica past in één zin: wanneer dit gebeurt, als deze voorwaarden gelden, doe dan dat. Een plugin is een lijst van regels, een regel is één trigger, nul tot 20 voorwaarden en één tot 50 acties.

Er wordt nergens Java gegenereerd, en er wordt niets gecompileerd: de server laadt een engine die de spec leest. Dat is de eigenschap die er later toe doet, want een nieuwe Minecraft-versie wordt maar één keer betaald, in die engine, en niet in elke plugin die er ooit mee is geschreven.

De vorm van een spec

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

match is all (standaard) of any, en een voorwaarde kan "not": true dragen. enabled: false houdt een regel in het bestand zonder hem te activeren.

De grammatica, in EBNF

De onderstaande lijsten met terminalen worden gegenereerd vanuit de catalogus: het zijn letterlijk de identifiers die de engine kan uitvoeren, geen met de hand gemaakte overschrijving.

<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"

Twee dingen zijn niet in deze productieregels te lezen, omdat ze over samenhang gaan en niet over vorm. Een voorwaarde of actie die een speler vereist, kan alleen worden geschreven onder een trigger die er een levert: "genees de speler" heeft geen betekenis onder "elke N seconden". En cancel_event kan alleen worden geschreven onder een annuleerbare trigger. Beide worden geweigerd bij het schrijven, niet bij het uitvoeren.

Sjablonen

Parameters van het type text zijn sjablonen: {player} wordt daarin vervangen door wat de trigger heeft geleverd, {{ geeft een letterlijk accolade-teken, en de kleurcodes &a &l worden vertaald.

Een variabele die de trigger niet kent, is een fout, geen tekst. Elke trigger geeft aan wat hij levert (zie de lijst hieronder): buiten die lijst is er niets te lezen, en een typefout wordt geweigerd bij het schrijven in plaats van in de chat van de spelers terecht te komen. De parameters van een trigger zelf zijn geen sjablonen: ze worden gelezen bij het opstarten van de server, voordat er iets te vervangen valt.

Minecraft-namen worden op twee manieren geschreven, afhankelijk van waar ze naar verwijzen. Een voorwerp wordt in beide schrijfwijzen genoemd (DIAMOND_SWORD of diamond_sword). Een geluid of een effect wordt genoemd via de Minecraft-sleutel (entity.player.levelup, speed) en niet via de Bukkit-constante: de sleutel gaat ongewijzigd naar de client en overleeft versiewisselingen.

Wat de grammatica niet toestaat

Ze is gesloten, en dat wordt eerst gezegd. Wat in de catalogus staat, is mogelijk, de rest is onmogelijk en blijft dat: er is geen enkele manier om willekeurige Java uit te voeren. Dat is wat de studio direct maakt (niets te compileren) en een spec onschadelijk om te installeren, ongeacht de herkomst.

Voorwaarden worden niet genest: dat is een bewuste beperking van versie 1, een booleaanse boom laat zich slecht tekenen in blokken, en all / any / not zijn bijna altijd genoeg. De enige uitweg is de actie "voer een commando uit als console", die op die server precies de macht van de console heeft, niet meer en niet minder.

Triggers

Wat een regel start, en de variabelen die elke trigger levert voor de sjablonen.

player.join Een speler verbindt

Op het moment dat de speler de server binnenkomt, nadat de wereld geladen is.

levert: {player} De speler die verbindt, {world} De wereld waarin hij aankomt

player.quit Een speler vertrekt

Op het moment dat de speler de server verlaat. Hij is dan nog bereikbaar, maar elk verzonden bericht gaat verloren.

levert: {player} De speler die vertrekt, {world} De wereld die hij verlaat

player.chat Een speler schrijft in de chat annuleerbaar

Voordat het bericht aan de andere spelers wordt afgeleverd. Annuleerbaar: het bericht wordt dan door niemand gezien.

levert: {player} Wie het bericht schreef, {world} Zijn wereld, {message} Het geschreven bericht

player.death Een speler sterft

Bij het overlijden van de speler, vóór het herrijzingsscherm.

levert: {player} De overleden speler, {world} Zijn wereld, {killer} De verantwoordelijke speler, leeg als die er niet is

player.respawn Een speler herrijst

Wanneer de speler na het overlijden terugkeert in het spel.

levert: {player} De speler die herrijst, {world} De herrijzingswereld

block.break Een speler breekt een blok annuleerbaar

Voordat het blok verdwijnt. Annuleerbaar: het blok blijft staan.

levert: {player} De speler die breekt, {world} Zijn wereld, {block} Het bloktype (DIAMOND_ORE), {x} X-coördinaat van het blok, {y} Hoogte van het blok, {z} Z-coördinaat van het blok

block.place Een speler plaatst een blok annuleerbaar

Voordat het blok geplaatst wordt. Annuleerbaar: het blok wordt niet geplaatst.

levert: {player} De speler die plaatst, {world} Zijn wereld, {block} Het geplaatste bloktype, {x} X-coördinaat van het blok, {y} Hoogte van het blok, {z} Z-coördinaat van het blok

command Een speler typt een commando

Maakt een nieuw commando aan op de server. ⚠️ Een commando waarvan de naam nog niet bestond, verschijnt pas na een herstart van de server: Paper accepteert nieuwe commandonamen alleen bij het opstarten. De inhoud van de regel wordt daarentegen wél direct herladen.

  • name text Naam van het commando, zonder de schuine streep
  • description text optioneel Beschrijving die in de help wordt getoond
  • permission text optioneel Vereiste permissie, leeg voor iedereen

levert: {player} De speler die het commando typte, {world} Zijn wereld, {args} Wat er na het commando volgt, precies zoals getypt

schedule.repeat Elke N seconden

Herhaalt de regel zolang de server draait. Er is geen speler bij betrokken: alleen acties die er geen nodig hebben, kunnen gebruikt worden.

  • seconds integer [1..86400] Interval in seconden

Voorwaarden

Wat filtert. Nul tot 20 per regel, gecombineerd via match.

player.has_permission De speler heeft de permissie heeft een speler nodig
  • permission text Permissie
player.is_op De speler is een operator heeft een speler nodig
player.in_world De speler is in de wereld heeft een speler nodig
  • world text Naam van de wereld
player.health_below De speler heeft minder dan X harten heeft een speler nodig

In levenspunten: 20 punten zijn 10 harten.

  • value number [0..1024] Levenspunten
text.contains Een tekst bevat
  • text text Onderzochte tekst
  • search text Wat je erin zoekt
  • ignoreCase boolean optioneel, standaard: true Hoofdletters negeren
text.equals Een tekst is gelijk aan
  • text text Onderzochte tekst
  • value text Verwachte waarde
  • ignoreCase boolean optioneel, standaard: true Hoofdletters negeren
number.compare Vergelijk twee getallen

Beide kanten zijn sjablonen: {y} wordt vergeleken met 62 zonder code te schrijven.

  • left text Linkerkant
  • operator < | <= | == | != | >= | > Vergelijking
  • right text Rechterkant
chance Willekeurig, X % van de tijd
  • percent number [0..100] Percentage

Acties

Wat er gebeurt. Eén tot 50 per regel, uitgevoerd in volgorde.

player.send_message Stuur een bericht naar de speler heeft een speler nodig
  • message text Bericht
player.send_actionbar Toon een bericht boven de actiebalk heeft een speler nodig
  • message text Bericht
player.send_title Toon een titel in grote letters heeft een speler nodig
  • title text Titel
  • subtitle text optioneel Ondertitel
  • fadeIn number [0..60] optioneel, standaard: 0.5 Verschijnen in seconden
  • stay number [0..600] optioneel, standaard: 3 Duur in seconden
  • fadeOut number [0..60] optioneel, standaard: 0.5 Verdwijnen in seconden
player.play_sound Speel een geluid af voor de speler heeft een speler nodig
  • sound identifier Geluid, als Minecraft-sleutel (entity.player.levelup)
  • volume number [0..10] optioneel, standaard: 1 Volume
  • pitch number [0.5..2] optioneel, standaard: 1 Toonhoogte
player.give_item Geef een voorwerp aan de speler heeft een speler nodig

Wat niet in de inventaris past, valt op de grond aan zijn voeten.

  • material identifier Voorwerp (DIAMOND_SWORD)
  • amount integer [1..2304] optioneel, standaard: 1 Aantal
player.give_effect Pas een effect toe op de speler heeft een speler nodig
  • effect identifier Effect, als Minecraft-sleutel (night_vision)
  • seconds integer [1..86400] optioneel, standaard: 10 Duur in seconden
  • amplifier integer [0..255] optioneel, standaard: 0 Niveau, vanaf 0
player.heal Genees de speler heeft een speler nodig

Leven op maximum, honger gestild.

player.teleport Teleporteer de speler heeft een speler nodig
  • x number [-30000000..30000000] X-coördinaat
  • y number [-512..1024] Hoogte
  • z number [-30000000..30000000] Z-coördinaat
  • world text optioneel Wereld, leeg voor die van de speler zelf
player.kick Kick de speler heeft een speler nodig
  • reason text Getoonde reden
broadcast Kondig aan op de hele server
  • message text Bericht
server.run_command Voer een commando uit als de console

De ontsnappingsroute van de grammatica: alles wat de console kan doen. Dat is de volledige macht van de console over deze server, schrijf dit dus alleen met kennis van zaken.

  • command text Commando, zonder de schuine streep
cancel_event Annuleer wat er net gebeurde heeft een annuleerbare trigger nodig

Bestaat alleen bij annuleerbare triggers: het bericht wordt niet afgeleverd, het blok wordt niet gebroken.

log Schrijf naar de serverconsole
  • message text Bericht
  • level info | warn optioneel, standaard: info Niveau
delay Wacht

Pauzeert de rest van de acties in de regel. Een regel die wacht, kan niets meer annuleren: "Annuleren" moet daarvoor komen.

  • seconds number [0.05..3600] Seconden

De gepubliceerde bestanden

Het JSON Schema wordt bij elke deploy gegenereerd vanuit de catalogus, en het is de machineleesbare referentie: een teksteditor die het volgt, vult identifiers automatisch aan en weigert onbekende parameters.

  • plugin-1.schema.json : het schema, in JSON Schema draft 2020-12.
  • catalogue-1.json : de catalogus zelf, de bron waar al de rest van afgeleid is (de validator, het schema, de blokken van de editor en de Java-engine).

De catalogus draagt de labels in het Frans, de brontaal. De identifiers zelf worden niet vertaald: zij zijn het wat de engine uitvoert.

Wat er gebeurt als de grammatica verandert

Een trigger, voorwaarde of actie toevoegen breekt niets: een eerder geschreven spec blijft gewoon draaien. Een breaking change daarentegen verhoogt specVersion, en de engine weigert dan wat hij niet begrijpt in plaats van het verkeerd uit te voeren, en meldt dat in de serverconsole.

De URL's hierboven dragen dat nummer: plugin-1.schema.json zal altijd naar deze grammatica verwijzen, ook op de dag dat er een andere bestaat.