メインコンテンツまでスキップ

シナリオを構成する要素

Scenamatica のシナリオを構成する要素について説明しています。


ヒント

このページでは, シナリオの具体的な要素について説明しています。
シナリオファイルの構文や実践的な書き方については以下のページを参照してください。

概要

シナリオは以下の要素で構成されています。

  • メタデータ
    Scenamatica がシナリオ(ファイル)を識別する情報について記述します。
  • トリガ
    シナリオテストが発火する条件について記述します。
  • 実行順
    シナリオテストを実行する順番について記述します。
    プラグインのスタートアップ時に実行されるシナリオなど, 同時に他のシナリオが複数発火する場合などに, その中での実行順を制御します。
  • コンテキスト
    シナリオテストを実行するために, 必要な前提環境(e.g. ワールド, プレイヤ, モブ・エンティティ)について記述します。
  • メインシナリオ
    プラグイン機能を発火させたり, 発火した機能を検証するためのフローを記述します。
  • 実行条件
    シナリオの実行に必要な条件(e.g. マイルストーン)について記述します。
  • 定数の定義
    使いまわしする値や構造体について記述します。

シナリオ

プラグイン開発者は, プラグインの機能の発火条件と, 想定される動作の一連のフローをシナリオとして記述します。
Scenamatica はこれらを一つ一つ実行・検証し, プラグインの振る舞いをテストします。 実行・検証に失敗した場合は, Scenamatica はプラグインが正しく動作していないと判断し, シナリオ実行の失敗を報告します。

各シナリオは、アクションシナリオタイプの対で構成されます。

アクション

アクションとは, 「プレイヤが死亡する」や「プレイヤがメッセージを送信する」などの, サーバにおける具体的な動作のことです。
これらは Scenamatica によってあらかじめ定義されたもので, プラグインの機能を最小単位に分解したものです。 アクションを組み合わせてシナリオを記述します。

アクションは膨大な数の種類があるので, 各カテゴリ別に分けられています。
すべてのアクションについてはScenamatica References | アクションの一覧を参照してください。

アクションはそれぞれ異なった入力を受け取り, またそれぞれ異なった出力を返します。
入力は with キーを使用して指定します。

例:

scenario:
# Actor1 に /hoge を実行させる。
- type: execute
action: command_dispatch
with:
command: "/hoge"
sender: Actor1

シナリオタイプ

全てのシナリオは, 以下の3つのタイプのいずれかに分類されます。

シナリオタイプ役割
アクション実行(execute)アクションを実行し, プラグインの機能を発火させる。
アクション実行期待(expect)アクションが実行されるまで待機し, プラグインの機能が正常に動作するかどうかを検証する。
条件要求(require)アクションが既に実行され, 状態が満たされているかどうかを検証する。

シナリオタイプは, そのシナリオの役割(機能の発火・機能のテストなど)を指定するとともに、そのシナリオに使用されるアクションの振る舞いを制御します。

例:

scenario:
# 1. Actor 1 をキルする。
- type: execute
action: player_death
with:
target: Actor1
# 2. Actor 1 がキルされることを期待する。
- type: expect
action: player_death
with:
target: Actor1
# 3. Actor 1 が死亡していることを要求する。
- type: require
action: player_death
with:
target: Actor1

上記のシナリオの例ではすべて「Actor1 がキルされる」という同じアクションを使用しています。 しかし, 対応するシナリオタイプが異なるために, 以下のようにそれぞれ異なって振る舞います。

シナリオタイプふるまい
1アクション実行(execute)プレイヤをキルする
2アクション実行期待(expect)プレイヤがキルされるまで待機する
3条件要求(require)プレイヤが(もう既に)死亡していることを要求する
警告

すべてのアクションが3のシナリオタイプすべてを持つわけではありません。
例えばアクション:ワールドの初期化は, 実行期待シナリオタイプにのみ対応しています。


アクション実行(execute) タイプ

アクション実行(execute) タイプは, 引数に指定されたアクションを実行し, 実行が完了するまで, またはタイムアウトするまで待機します。
これはテスト対象のプラグイン機能の実行を発火させるために使用します。

  • シナリオが成功する条件
    1. アクションの実行が例外またはエラーなしで完了する。
  • シナリオが失敗する条件
    1. アクションの実行がタイムアウトする。
    2. アクションの実行時に例外またはエラーが発生する。

例:

scenario:
# Actor1 に /hoge を実行させる。
- type: execute
action: command_dispatch
with:
command: "/hoge"
sender: Actor1
timeout: 1000 # タイムアウトを 1000 チックに設定

アクション実行期待(expect) タイプ

アクション実行期待(expect) タイプは, アクションがプラグインによって実行されるかどうかを監視します。
指定されたアクションが実行されるまで, もしくはタイムアウトするまで次のシナリオの実行を待機します。

  • シナリオが成功する条件
    1. 対象のプラグインによってアクションが実行される。
  • シナリオが失敗する条件
    1. 検証がタイムアウトする。
    2. 後述された実行期待シナリオ(expect)のアクションが先に実行される(詳細後述)。
test-hoge-success.yml
scenario:
# fuga というメッセージが(Actor1 に)表示されることを期待する。
- type: expect
action: message
with:
content: "fuga"
recipient: Actor1
# Actor1 がキルされることを期待する。
- type: expect
action: player_death
with:
target: Actor1
後に実行されるべきアクションが繰り上げられて先に実行された場合は, このシナリオは失敗します。

例えば, 以下のようなシナリオについて考えます:

  1. アクション A を実行する
  2. アクション B の実行を(プラグインによって行われるか)期待する
  3. アクション C を実行を(〃)期待する

このとき, プラグインが機能(アクション)を C -> B の順番で実行した場合は, このシナリオは C の実行とともに失敗します。

ただし, 実行が失敗するのは, アクションの実行順序が不正な場合のみです。
例えば「指定されたメッセージ "Hoge" が送信されること」を後に期待している場合に メッセージ "Fuga" が送信されたとしても, このシナリオは即座に失敗しません
そのあとに正しいメッセージが時間内に送信された場合は, このシナリオは成功します。

上記の例で云うところの, メッセージ fuga が送信される前に, プレイヤが死亡してしまった場合は, このシナリオは失敗します。


条件要求(require) タイプ

条件要求(require) タイプは, 引数に指定された条件が既に満たされているかどうかを検証します。
Scenamatica はシナリオを上から順に実行しますから, このタイプを使っているシナリオに到達する前までに条件が満たされていない場合は, そのシナリオは失敗します。

シナリオ内以外の使用法としては, これは条件付き実行の条件の指定に使用します。

  • シナリオが成功する条件
    1. 条件がこのシナリオの到達時に満たされている。
  • シナリオが失敗する条件
    1. 条件がこのシナリオの到達時に満たされていない。
    2. 条件がこのシナリオの到達後に満たされる。

例えば, 以下のようなシナリオについて考えます:

  1. アクション A を実行する
  2. プレイヤ P がアイテム X を持っていること(条件)を要求する
  3. アクション B を実行する

このシナリオでは, シナリオ 2 の到達時に Scenamatica はプレイヤ P がアイテム X を持っているかどうかを確認します。
シナリオ 1 の実行完了と同じタイミング(同チック)時点で, 指定された条件が満たされていない場合は, このシナリオは失敗します。

警告

このタイプはそのシナリオに到達する前に条件を満たしていることを確認するためのものです。
そのために, 満たされるまで待機したり, タイムアウトまで待機する機能はありません。 このような機能を使用したい場合は, 代わりにアクション実行期待(expect) タイプを使用してください。

scenario:
# Actor1 が死亡していることを要求する。
- type: require
action: player_death
with:
target: Actor1

実行順

この機能を使用すれば, 同じシナリオセッション内でのシナリオの実行順序を制御できます。

シナリオの実行順序は, シナリオファイルの order プロパティによって制御されます。
これを設定しない場合や, 順次指定が重複した場合は, 自動的にシナリオの名前順でソートされます。

例えば, 以下のシナリオが同じシナリオセッションに追加されている時, order プロパティによって実行順序が制御され, test-fuga-success.yml が最初に実行されます。

name: "test-hoge-success"
description: "hoge 機能の正常系テスト"

order: 2
# ...
name: "test-fuga-success"
description: "fuga 機能の正常系テスト"

order: 1
# ...
実行順序の制御は同一セッション内でのみ有効です。

例えば, 以下のようなシナリオと, それらが所属するセッションを考えます。

  • シナリオA - order: 1
  • シナリオB - order: 2
  • シナリオC - order: 3
  • シナリオD - (未設定)
  • セッション[0]
    • シナリオD
    • シナリオA
    • シナリオC
  • セッション[1]
    • シナリオD
    • シナリオB

セッションはキューに追加された順に実行されますから, 最終的なシナリオの実行順は {D -> A -> C} -> {D -> B} となります。

コンテキスト

コンテキストは, シナリオを実行するワールド(ステージという)の環境や, シナリオに登場する擬似的なプレイヤ(アクタという), モブ(エンティティという)などのシナリオテストの実行に必要な前提環境を指定します。 これらはシナリオの実行前に生成され, 終了時に破棄されます。

ヒント

1つのシナリオに1つのコンテキストが割り当てられます。
また, 事前・事後シナリオ機能で実行されるシナリオは, メインシナリオと同じコンテキストを使用します。

ステージ

ステージは, シナリオ を実行するワールドのことです。シナリオ開始時に生成され, 終了時にディレクトリごと削除されます。
この機能は, シナリオ実行時のワールドの変更を完全にインスタンス化して隔離し, 実行後に元のワールドの状態を元に戻すために用意されています。

ステージの作成は以下の2方式から選択できます:

  1. 既存の Minecraft ワールドをコピーする。
    既にサーバに存在するワールドをコピーすることで, 建築物やエンティティをそのまま利用できます。
  2. シード値や環境等を指定して新規に生成する。
    この方式はシナリオの実行時に毎回ワールドを生成するので, 毎回違う環境で実行できます。ただし, ワールドの生成はパフォーマンスに影響するため注意してください。

ステージの指定は, シナリオファイルcontext.stage で指定します。

例:

context:
# 方式 1. 既存の Minecraft ワールドをコピーする 場合
stage:
copyOf: world_nether
# -----------------------------------------------
# 方式 2. シード値や環境等を指定して新規に生成する。
stage:
type: FLAT
env: NETHER

アクタ

アクタは, シナリオ に登場する擬似的なプレイヤです。シナリオ開始時に作成され, 終了時に破棄されます。 また, アクタのログアウト時には, 実績や権限, ほかのすべてのアクタに関するプレイヤデータは削除されます。

この機能で生成されるプレイヤは, 通常のプレイヤのように Minecraft サーバにログインします。 しかしながら実際の通信は行われず, NMS を使用して Minecraft サーバの通信をエミュレートします。
そのため, アクタに対して送られるパケットは, Packet(In/Out)KeepAlive を除いて全て破棄されるか, または Scenamatica によって処理されます。

ヒント

Bukkit には, プラグイン側から PlayerInventory#setHeldItemSlot(int) などを使用した場合に PlayerItemHeldEvent が発火しないバグが存在します。 そのため, Scenamatica ではこれを回避し, アクタに対するスロットの変更時に PlayerItemHeldEvent を発火させるようにしています。

ただし, 同イベントのフィールドである previousSlot は技術的制約により currentSlot と同じ値になります。

アクタの指定は, シナリオファイルcontext.actors[] で指定します。

context:
actors:
- name: Actor1
permissions:
- "hogeplugin.commands.hoge"
flying: true

エンティティ

エンティティは, シナリオに登場するモブのことです。ステージの準備後に生成され, 終了時に破棄されます。
ステージに実際に生成されるため, 通常の Minecraft エンティティと同様に, 他のエンティティやプレイヤとの相互作用が可能です。 また, これらはシナリオ内で利用するために最適化されており, ワールドの他のエンティティよりもより早く, かつ優先的に解決されます。

Scenamatica はエンティティが削除されるまでこれを継続して追跡し, そのエンティティを含むチャンクを強制的に読み込みし続けます。

エンティティ指定は, シナリオファイルcontext.entities[] で指定します。

context:
entities:
- type: ZOMBIE
name: "Zombie1"
location:
x: 0
y: 64
z: 0
ヒント

指定するエンティティの型は, エンティティの種類によって異なります。
例えば, LivingEntity を実装するエンティティを指定した場合は, そのプロパティも指定できます。

Minecraft 1.17 以降の仕様について

Minecraft 1.17 以降は, Bukkit の制約により, ワールドのエンティティはメインスレッドからしか取得できません。
ゆえに, この機能を利用せずして作成されたエンティティの解決は, 1.17 以降のバージョンでは非推奨となります。

この機能を利用して作成されたエンティティは, 非同期処理によって解決されるため, シナリオに登場するエンティティはこの機能を使用して作成することを推奨します。

条件付き実行

指定する条件を満たした場合にのみシナリオを実行します。
実行条件が満たされていない場合そのシナリオはスキップされますが, 失敗とはみなされません。 また, 条件はシナリオファイルごと, またはその中のトリガやシナリオごとに指定できます。

条件の指定には, 条件要求(require) タイプのシナリオに対応したアクションを使用します。
詳しくは条件要求タイプ(require)およびアクションの項目を参照してください。

さらにこの中でも特に, トリガにおける条件付け機能は条件付きトリガ機能と呼ばれます。

ヒント

シナリオ及び各トリガの実行条件は, コンテキストの準備後に評価されます。
これは, 条件の種類によってはコンテキストが必要な場合があるためです。

例:

# ...

on:
- type: manual_dispatch
# トリガごとの実行条件を指定
runif:
action: player_death
with:
target: Actor1

# シナリオファイル全体に対する実行条件を指定
runif:
action: milestone
with:
name: hoge-milestone

scenario:
# [マイルストーン fuga を達成している場合に] Actor1 に /hoge を実行させる。
- type: execute
action: command_dispatch
with:
command: "/hoge"
sender: Actor1
# シナリオごとの実行条件を指定
runif:
action: milestone
with:
name: fuga-milestone

# fuga というメッセージが(Actor1 に)表示されることを期待する。
- type: execute
action: command_dispatch
with:
command: "/fuga"
sender: Actor1