プラグインの文法
specVersion 1
NimBlockのプラグインはコードではない。ルールを記述したJSONファイルであり、みんなのために 一度だけ書かれたPaperエンジンがそれを読み込んで実行する。このページはそこに書いてよいこと を正確に示すもので、それ以外は一切できない。
ルールはひとつ、それだけ
文法のすべてはこの一文に収まる:これが起きたとき、これらの条件が満たされて いれば、これを行う。プラグインとはルールの一覧であり、ひとつのルールは トリガーがひとつ、条件が0個から20個、アクションが1個から 50個で構成される。
どこかでJavaが生成されるわけでも、コンパイルされるわけでもない。サーバーはspecを 読み込むエンジンを起動するだけだ。これが後々効いてくる性質で、新しい Minecraftのバージョンへの対応コストはこのエンジンで一度だけ支払えばよく、そこで 書かれた個々のプラグインごとには発生しない。
specの形
{
"specVersion": 1,
"name": "ようこそ",
"rules": [
{
"id": "welcome",
"name": "ようこそメッセージ",
"match": "all",
"trigger": { "type": "player.join" },
"conditions": [
{ "type": "player.has_permission", "params": { "permission": "nimblock.vip" } }
],
"actions": [
{ "type": "player.send_message", "params": { "message": "&6ようこそ、{player}さん!" } }
]
}
]
}
matchはall(既定)かanyのどちらかで、条件
には"not": trueを付けられる。enabled: falseはルールを
ファイルに残したまま無効化する。
文法、EBNFで
下にある終端記号の一覧はカタログから生成されたものだ。つまりエンジンが実行できる 識別子そのものであり、手作業で書き写したものではない。
<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"
この生成規則には表れない事柄が2つある。どちらも形ではなく整合性にかかわるものだ
からだ。プレイヤーを要求する条件やアクションは、プレイヤーを渡すトリガーの下でしか
書けない。「プレイヤーを回復する」は「N秒ごと」の下では意味を持たない。そして
cancel_eventはキャンセル可能なトリガーの下でしか書けない。どちらも
実行時ではなく、書いた時点で拒否される。
テンプレート
text型のパラメーターはテンプレートだ:{player}
はトリガーが渡した値に置き換えられ、{{はブレース自体を
表し、カラーコード&a &lは変換される。
トリガーが知らない変数を使うとエラーになる。テキストとしてそのまま出力 されることはない。各トリガーは自分が渡す変数を宣言しており(下の一覧を 参照)、その一覧にないものは何も読み出せない。タイプミスはプレイヤーのチャットに 現れる前に、書いた時点で拒否される。一方トリガー自身のパラメーターは テンプレートではない。サーバー起動時に読み込まれるもので、その時点では置き換える べきものがまだ何もない。
Minecraftの名前は、何を指すかによって2通りの書き方がある。アイテム
はどちらの表記でも通る(DIAMOND_SWORDまたはdiamond_sword)。
サウンドやエフェクトはBukkitの定数ではなく
Minecraft側のキーで指定する(entity.player.levelup、speed)。
このキーはそのままクライアントに送られ、バージョンが変わっても残り続ける。
この文法でできないこと
この文法は閉じている。まずそれを言っておく。カタログに載って いることはできる、それ以外はできないし、これからもできない。任意のJavaを実行する 手段は存在しない。だからこそスタジオは即座に動き(コンパイルするものが何もない)、 specはどこから来たものであっても、インストールしても害がない。
条件は入れ子にできない。これはバージョン1で割り切った制限で、
ブール木をブロックで表現すると形が崩れやすく、all / any /
notでほとんどの場合は事足りる。唯一の抜け道は「コンソールとして
コマンドを実行する」アクションで、これはそのサーバー上でコンソールが持つ権限を
そのまま持つ。それ以上でも以下でもない。
トリガー
ルールを起動するもの、そしてそれぞれがテンプレート用に渡す変数。
player.join
プレイヤーが参加
プレイヤーがサーバーに参加した瞬間。ワールドの読み込みが終わった後。
提供:
{player} 参加したプレイヤー,
{world} 参加先のワールド
player.quit
プレイヤーが退出
プレイヤーがサーバーを退出した瞬間。まだ通信は届くが、送信したメッセージはすべて失われる。
提供:
{player} 退出したプレイヤー,
{world} 退出したワールド
player.chat
プレイヤーがチャットに書き込む
キャンセル可能
メッセージが他のプレイヤーに配信される前。キャンセル可能:キャンセルするとメッセージは誰にも見えなくなる。
提供:
{player} 書き込んだプレイヤー,
{world} そのワールド,
{message} 書き込まれたメッセージ
player.death
プレイヤーが死亡
プレイヤーが死亡した瞬間、リスポーン画面が表示される前。
提供:
{player} 死亡したプレイヤー,
{world} そのワールド,
{killer} 死因となったプレイヤー、いなければ空
player.respawn
プレイヤーがリスポーン
プレイヤーが死亡後にゲームへ戻ったとき。
提供:
{player} リスポーンしたプレイヤー,
{world} リスポーン先のワールド
block.break
プレイヤーがブロックを破壊
キャンセル可能
ブロックが消える前。キャンセル可能:キャンセルするとブロックはそのまま残る。
提供:
{player} 破壊したプレイヤー,
{world} そのワールド,
{block} ブロックの種類(DIAMOND_ORE),
{x} ブロックのX座標,
{y} ブロックの高さ,
{z} ブロックのZ座標
block.place
プレイヤーがブロックを設置
キャンセル可能
ブロックが設置される前。キャンセル可能:キャンセルするとブロックは設置されない。
提供:
{player} 設置したプレイヤー,
{world} そのワールド,
{block} 設置されたブロックの種類,
{x} ブロックのX座標,
{y} ブロックの高さ,
{z} ブロックのZ座標
command
プレイヤーがコマンドを入力
サーバーに新しいコマンドを作成する。⚠️ まだ存在しない名前のコマンドは、サーバーを再起動するまで反映されない:Paperは起動時にしか新しいコマンド名を受け付けない。ルールの中身は、再起動なしでそのまま反映される。
-
nametext コマンド名(スラッシュなし) -
descriptiontext 省略可 ヘルプに表示される説明 -
permissiontext 省略可 必要な権限、空なら誰でも使える
提供:
{player} コマンドを入力したプレイヤー,
{world} そのワールド,
{args} コマンドに続く部分、そのまま
schedule.repeat
N秒ごと
サーバーが動いている間、ルールを繰り返す。プレイヤーは関係しない:プレイヤーを必要としないアクションしか使えない。
-
secondsinteger [1..86400] 間隔(秒)
条件
絞り込むもの。1ルールにつき0個から20個まで、matchで組み合わせる。
player.has_permission
プレイヤーが権限を持っている
プレイヤーが必要
-
permissiontext 権限
player.is_op
プレイヤーがOPである
プレイヤーが必要
player.in_world
プレイヤーがそのワールドにいる
プレイヤーが必要
-
worldtext ワールド名
player.health_below
プレイヤーの体力がXハート未満
プレイヤーが必要
体力ポイントで指定する:20ポイントが10ハートにあたる。
-
valuenumber [0..1024] 体力ポイント
text.contains
文字列を含む
-
texttext 調べる文字列 -
searchtext 検索する文字列 -
ignoreCaseboolean 省略可, 既定値:true大文字と小文字を区別しない
text.equals
文字列が等しい
-
texttext 調べる文字列 -
valuetext 期待する値 -
ignoreCaseboolean 省略可, 既定値:true大文字と小文字を区別しない
number.compare
2つの数値を比較
両辺はテンプレート:{y}を62と比較するのにコードは要らない。
-
lefttext 左辺 -
operator< | <= | == | != | >= | > 比較演算子 -
righttext 右辺
chance
ランダムでX%の確率
-
percentnumber [0..100] パーセンテージ
アクション
実際に起きること。1ルールにつき1個から50個まで、順番に実行される。
player.send_message
プレイヤーにメッセージを送る
プレイヤーが必要
-
messagetext メッセージ
player.send_actionbar
アクションバーの上にメッセージを表示
プレイヤーが必要
-
messagetext メッセージ
player.send_title
大きなタイトルを表示
プレイヤーが必要
-
titletext タイトル -
subtitletext 省略可 サブタイトル -
fadeInnumber [0..60] 省略可, 既定値:0.5フェードイン(秒) -
staynumber [0..600] 省略可, 既定値:3表示時間(秒) -
fadeOutnumber [0..60] 省略可, 既定値:0.5フェードアウト(秒)
player.play_sound
プレイヤーに音を再生
プレイヤーが必要
-
soundidentifier 音、Minecraftのキーで指定(entity.player.levelup) -
volumenumber [0..10] 省略可, 既定値:1音量 -
pitchnumber [0.5..2] 省略可, 既定値:1ピッチ
player.give_item
プレイヤーにアイテムを渡す
プレイヤーが必要
インベントリに入りきらない分は足元に落ちる。
-
materialidentifier アイテム(DIAMOND_SWORD) -
amountinteger [1..2304] 省略可, 既定値:1個数
player.give_effect
プレイヤーに効果を付与
プレイヤーが必要
-
effectidentifier 効果、Minecraftのキーで指定(night_vision) -
secondsinteger [1..86400] 省略可, 既定値:10持続時間(秒) -
amplifierinteger [0..255] 省略可, 既定値:0レベル(0から)
player.heal
プレイヤーを回復
プレイヤーが必要
体力を最大に、満腹度も満たす。
player.teleport
プレイヤーをテレポート
プレイヤーが必要
-
xnumber [-30000000..30000000] X座標 -
ynumber [-512..1024] 高さ -
znumber [-30000000..30000000] Z座標 -
worldtext 省略可 ワールド、空ならプレイヤーがいるワールド
player.kick
プレイヤーをキック
プレイヤーが必要
-
reasontext 表示される理由
broadcast
サーバー全体にアナウンス
-
messagetext メッセージ
server.run_command
コンソールとしてコマンドを実行
文法の逃げ道:コンソールにできることは何でもできる。このサーバーに対するコンソールの権限そのものなので、わかったうえで書くこと。
-
commandtext コマンド(スラッシュなし)
cancel_event
直前の出来事をキャンセル
キャンセル可能なトリガーが必要
キャンセル可能な発動条件にしか存在しない:メッセージは配信されず、ブロックは壊れない。
log
サーバーのコンソールに書き込む
-
messagetext メッセージ -
levelinfo | warn 省略可, 既定値:infoレベル
delay
待つ
ルールの残りのアクションを一時停止する。待った後のルールはもう何もキャンセルできない:「キャンセル」はそれより前に置くこと。
-
secondsnumber [0.05..3600] 秒数
公開されているファイル
JSON Schemaはデプロイのたびにカタログから生成される、機械可読な 参照仕様だ。これに従うエディターなら識別子を補完し、未知のパラメーターを拒否できる。
- plugin-1.schema.json :スキーマ本体。JSON Schema draft 2020-12。
- catalogue-1.json :カタログそのもの。他のすべて(バリデーター、スキーマ、エディターのブロック、 Javaエンジン)がここから導かれる元データ。
カタログのラベルはソース言語であるフランス語で書かれている。一方、識別子は翻訳 されない。エンジンが実行するのはこの識別子そのものだからだ。
文法が変わるとき何が起きるか
トリガー・条件・アクションを追加しても何も壊れない。以前に書かれたspecはそのまま
動き続ける。一方破壊的変更はspecVersionを繰り上げ、
以降エンジンは理解できないものを誤って実行するのではなく拒否し、サーバーの
コンソールにそう表示する。
上のURLはこの番号を含んでいる。plugin-1.schema.jsonは、
たとえ別の文法が存在するようになった日が来ても、常にこの文法を指し続ける。