NimBlock Zaloguj się

Gramatyka pluginów

specVersion 1

Plugin NimBlock to nie kod: to plik JSON opisujący reguły, odczytywany i wykonywany przez silnik Paper napisany raz dla wszystkich. Ta strona mówi dokładnie, co wolno w nim zapisać, i nic poza tym nie jest możliwe.

Jedna reguła i nic poza regułą

Cała gramatyka mieści się w jednym zdaniu: gdy to się wydarzy, jeśli te warunki są spełnione, wtedy zrób to. Plugin to lista reguł, reguła to jeden wyzwalacz, od zera do 20 warunków i od jednej do 50 akcji.

Nigdzie nie powstaje żaden kod Java i nic nie jest kompilowane: serwer wczytuje silnik, który odczytuje specyfikację. To ta właściwość liczy się na przyszłość, bo koszt nowej wersji Minecrafta płaci się raz, w tym silniku, a nie w każdym pluginie, który za jego pomocą powstał.

Jak wygląda specyfikacja

{
    "specVersion": 1,
    "name": "Witaj",
    "rules": [
        {
            "id": "powitanie",
            "name": "Wiadomość powitalna",
            "match": "all",
            "trigger": { "type": "player.join" },
            "conditions": [
                { "type": "player.has_permission", "params": { "permission": "nimblock.vip" } }
            ],
            "actions": [
                { "type": "player.send_message", "params": { "message": "&6Witaj {player}!" } }
            ]
        }
    ]
}

match przyjmuje wartość all (domyślnie) lub any, a warunek może mieć ustawione "not": true. enabled: false zachowuje regułę w pliku, nie włączając jej.

Gramatyka w zapisie EBNF

Poniższe listy terminali są generowane z katalogu: to dosłownie identyfikatory, które silnik potrafi wykonać, a nie transkrypcja zrobiona ręcznie.

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

Dwóch rzeczy nie widać w tych regułach produkcji, bo dotyczą spójności, a nie formy. Warunek lub akcja wymagające gracza dają się zapisać tylko pod wyzwalaczem, który go dostarcza: „ulecz gracza” nie ma sensu pod „co N sekund”. A cancel_event da się zapisać tylko pod wyzwalaczem, który można anulować. Oba przypadki są odrzucane już przy zapisie, nie dopiero przy wykonaniu.

Szablony

Parametry typu text to szablony: {player} zostaje w nich zastąpiony tym, co dostarczył wyzwalacz, {{ daje dosłowny nawias klamrowy, a kody kolorów &a &l są tłumaczone.

Zmienna nieznana wyzwalaczowi to błąd, a nie tekst. Każdy wyzwalacz deklaruje, co dostarcza (zobacz listę poniżej): poza tą listą nie ma nic do odczytania, a literówka jest odrzucana już przy zapisie, zamiast trafić na czat graczy. Parametry samego wyzwalacza nie są natomiast szablonami: są odczytywane przy starcie serwera, zanim będzie cokolwiek do podstawienia.

Nazwy z Minecrafta zapisuje się na dwa sposoby, zależnie od tego, co oznaczają. Przedmiot nazywa się w obu zapisach (DIAMOND_SWORD lub diamond_sword). Dźwięk lub efekt nazywa się swoim kluczem Minecrafta (entity.player.levelup, speed), a nie stałą Bukkit: klucz trafia do klienta w niezmienionej postaci i przetrwa zmiany wersji.

Czego gramatyka nie pozwala

Jest zamknięta i to trzeba powiedzieć jako pierwsze. To, co znajduje się w katalogu, jest możliwe do zrobienia, reszta jest niemożliwa i taka pozostanie: nie ma żadnego sposobu na wykonanie dowolnego kodu Java. Dzięki temu studio działa natychmiast (nie ma nic do skompilowania), a specyfikacja jest bezpieczna do zainstalowania, bez względu na to, skąd pochodzi.

Warunki nie zagnieżdżają się: to świadome ograniczenie wersji 1, drzewo logiczne źle się rysuje w klockach, a all / any / not niemal zawsze wystarczają. Jedynym wyjściem jest akcja „wykonaj polecenie jako konsola”, która na danym serwerze ma dokładnie taką moc, jak konsola, ani więcej, ani mniej.

Wyzwalacze

To, co uruchamia regułę, oraz zmienne, które każdy z nich dostarcza szablonom.

player.join Gracz dołącza

W momencie, gdy gracz wchodzi na serwer, po wczytaniu jego świata.

udostępnia: {player} Gracz, który dołącza, {world} Świat, do którego trafia

player.quit Gracz wychodzi

W momencie, gdy gracz opuszcza serwer. Jest jeszcze osiągalny, ale każda wysłana wiadomość zostaje utracona.

udostępnia: {player} Gracz, który wychodzi, {world} Świat, który opuszcza

player.chat Gracz pisze na czacie można anulować

Zanim wiadomość zostanie rozesłana do innych graczy. Można anulować: wtedy wiadomości nie zobaczy nikt.

udostępnia: {player} Autor wiadomości, {world} Jego świat, {message} Napisana wiadomość

player.death Gracz ginie

W chwili śmierci gracza, przed ekranem odrodzenia.

udostępnia: {player} Zmarły gracz, {world} Jego świat, {killer} Odpowiedzialny gracz, puste, jeśli go nie ma

player.respawn Gracz się odradza

Gdy gracz wraca do gry po śmierci.

udostępnia: {player} Gracz, który się odradza, {world} Świat odrodzenia

block.break Gracz niszczy blok można anulować

Zanim blok zniknie. Można anulować: blok zostaje na miejscu.

udostępnia: {player} Gracz, który niszczy, {world} Jego świat, {block} Typ bloku (DIAMOND_ORE), {x} Współrzędna X bloku, {y} Wysokość bloku, {z} Współrzędna Z bloku

block.place Gracz stawia blok można anulować

Zanim blok zostanie postawiony. Można anulować: blok nie zostaje postawiony.

udostępnia: {player} Gracz, który stawia, {world} Jego świat, {block} Typ postawionego bloku, {x} Współrzędna X bloku, {y} Wysokość bloku, {z} Współrzędna Z bloku

command Gracz wpisuje polecenie

Tworzy nowe polecenie na serwerze. ⚠️ Polecenie o nazwie, która wcześniej nie istniała, pojawia się dopiero po ponownym uruchomieniu serwera: Paper przyjmuje nowe nazwy poleceń tylko przy starcie. Treść reguły natomiast wczytuje się na bieżąco, bez restartu.

  • name text Nazwa polecenia, bez ukośnika
  • description text opcjonalne Opis wyświetlany w pomocy
  • permission text opcjonalne Wymagane uprawnienie, puste dla wszystkich

udostępnia: {player} Gracz, który wpisał polecenie, {world} Jego świat, {args} To, co następuje po poleceniu, w niezmienionej postaci

schedule.repeat Co N sekund

Powtarza regułę, dopóki serwer działa. Nie dotyczy żadnego gracza: da się użyć tylko akcji, które go nie wymagają.

  • seconds integer [1..86400] Odstęp w sekundach

Warunki

To, co filtruje. Od zera do 20 na regułę, łączone przez match.

player.has_permission Gracz ma uprawnienie wymaga gracza
  • permission text Uprawnienie
player.is_op Gracz jest operatorem wymaga gracza
player.in_world Gracz jest w świecie wymaga gracza
  • world text Nazwa świata
player.health_below Gracz ma mniej niż X serc wymaga gracza

W punktach zdrowia: 20 punktów to 10 serc.

  • value number [0..1024] Punkty zdrowia
text.contains Tekst zawiera
  • text text Sprawdzany tekst
  • search text Czego szukamy
  • ignoreCase boolean opcjonalne, domyślnie: true Ignoruj wielkość liter
text.equals Tekst jest równy
  • text text Sprawdzany tekst
  • value text Oczekiwana wartość
  • ignoreCase boolean opcjonalne, domyślnie: true Ignoruj wielkość liter
number.compare Porównaj dwie liczby

Obie strony to szablony: {y} porównuje się z 62 bez pisania kodu.

  • left text Lewa strona
  • operator < | <= | == | != | >= | > Porównanie
  • right text Prawa strona
chance Losowo, przez X % czasu
  • percent number [0..100] Procent

Akcje

To, co się dzieje. Od jednej do 50 na regułę, wykonywane po kolei.

player.send_message Wyślij wiadomość do gracza wymaga gracza
  • message text Wiadomość
player.send_actionbar Pokaż wiadomość nad paskiem akcji wymaga gracza
  • message text Wiadomość
player.send_title Pokaż duży tytuł wymaga gracza
  • title text Tytuł
  • subtitle text opcjonalne Podtytuł
  • fadeIn number [0..60] opcjonalne, domyślnie: 0.5 Pojawianie się w sekundach
  • stay number [0..600] opcjonalne, domyślnie: 3 Czas trwania w sekundach
  • fadeOut number [0..60] opcjonalne, domyślnie: 0.5 Znikanie w sekundach
player.play_sound Odtwórz dźwięk dla gracza wymaga gracza
  • sound identifier Dźwięk, jako klucz Minecrafta (entity.player.levelup)
  • volume number [0..10] opcjonalne, domyślnie: 1 Głośność
  • pitch number [0.5..2] opcjonalne, domyślnie: 1 Wysokość dźwięku
player.give_item Daj graczowi przedmiot wymaga gracza

To, co nie mieści się w ekwipunku, spada na ziemię pod jego nogami.

  • material identifier Przedmiot (DIAMOND_SWORD)
  • amount integer [1..2304] opcjonalne, domyślnie: 1 Ilość
player.give_effect Nałóż efekt na gracza wymaga gracza
  • effect identifier Efekt, jako klucz Minecrafta (night_vision)
  • seconds integer [1..86400] opcjonalne, domyślnie: 10 Czas trwania w sekundach
  • amplifier integer [0..255] opcjonalne, domyślnie: 0 Poziom, od 0
player.heal Ulecz gracza wymaga gracza

Pełne zdrowie, najedzenie do syta.

player.teleport Teleportuj gracza wymaga gracza
  • x number [-30000000..30000000] Współrzędna X
  • y number [-512..1024] Wysokość
  • z number [-30000000..30000000] Współrzędna Z
  • world text opcjonalne Świat, puste dla świata gracza
player.kick Wyrzuć gracza wymaga gracza
  • reason text Wyświetlany powód
broadcast Ogłoś na cały serwer
  • message text Wiadomość
server.run_command Wykonaj polecenie jako konsola

Furtka wyjściowa gramatyki: wszystko, co potrafi konsola. To pełna władza konsoli nad tym serwerem, więc pisz to świadomie.

  • command text Polecenie, bez ukośnika
cancel_event Anuluj to, co właśnie się stało wymaga wyzwalacza, który można anulować

Istnieje tylko przy anulowalnych wyzwalaczach: wiadomość nie zostaje rozesłana, blok nie zostaje zniszczony.

log Zapisz w konsoli serwera
  • message text Wiadomość
  • level info | warn opcjonalne, domyślnie: info Poziom
delay Poczekaj

Wstrzymuje kolejne akcje reguły. Reguła, która czeka, nie może już niczego anulować: „Anuluj” musi wystąpić wcześniej.

  • seconds number [0.05..3600] Sekundy

Opublikowane pliki

JSON Schema jest generowany z katalogu przy każdym wdrożeniu i stanowi referencję czytelną dla maszyny: edytor tekstu, który się nim posługuje, podpowiada identyfikatory i odrzuca nieznane parametry.

  • plugin-1.schema.json : schemat, w formacie JSON Schema draft 2020-12.
  • catalogue-1.json : sam katalog, źródło, z którego wywodzi się wszystko inne (walidator, schemat, klocki edytora i silnik Java).

Katalog zawiera etykiety w języku francuskim, który jest językiem źródłowym. Same identyfikatory nie są tłumaczone: to one są wykonywane przez silnik.

Co się dzieje, gdy gramatyka się zmienia

Dodanie wyzwalacza, warunku lub akcji niczego nie psuje: specyfikacja napisana wcześniej dalej działa. Zmiana łamiąca kompatybilność zwiększa natomiast specVersion, a silnik odmawia wtedy wykonania tego, czego nie rozumie, zamiast wykonać to błędnie, informując o tym w konsoli serwera.

Powyższe adresy URL noszą ten numer: plugin-1.schema.json zawsze będzie oznaczać tę właśnie gramatykę, nawet gdy pewnego dnia powstanie kolejna.