A sample text widget

Etiam pulvinar consectetur dolor sed malesuada. Ut convallis euismod dolor nec pretium. Nunc ut tristique massa.

Nam sodales mi vitae dolor ullamcorper et vulputate enim accumsan. Morbi orci magna, tincidunt vitae molestie nec, molestie at mi. Nulla nulla lorem, suscipit in posuere in, interdum non magna.

[Sponge]ブロックイベントとCauseのメモ

Spongeプラグインを作ってると意外と難儀なのがブロックが壊れた(ChangeBlockEvent.Break)とか、置かれた(ChangeBlockEvent.Place)の扱い。個人的にこの扱いがなかなか慣れないのでメモしておきます。本当はEntityも交えるといいんだけど、わかりづらいので、Entityは扱いません。

前提

イベントリスナの書き方

本題に入る前に、、、イベントを扱う場合の書き方はこんな感じです。以下の例はブロックを壊したら呼び出されます。

@Listener
public void onBlockEvent( ChangeBlockEvent.Break event ){
  //処理
}

プラグインのメインクラス(メタデータの@Pluginを定義したクラス)はこれだけで動きます。メインクラス以外はregisterListenersとかで登録してあげないと扱えません。

詳しい書き方などはSponge Pluginに書いてますのでそちらを参照下さい。
また、ここで主に扱うのはChangeBlockEventというブロックに変化が発生した時のイベントです。この辺のイベントの種類はあまり詳しくは無いですが、Sponge Events(API5.0)にリストにしてありますのでそっちを見て下さい。

Cause

ブロック系のイベントを扱う場合の重要な処理の一つです。
Causeはケース。そのイベントはどういうケース(原因)で発生したかを知る手段になります。例えば炎(ブロック)は延焼した時に、その着火者をCauseから知ることができます。

例えばそのイベントの発生がプレイヤー起因であるかを調べる場合は以下の通りです。

if ( event.getCause().get( NamedCause.SOURCE, Player.class).isPresent() ){
  // Player p = event.getCause().get( NamedCause.SOURCE, Player.class).get();
  // などの処理
}

NamedCause.SOURCE以外にもNamedCause.NORTIFIREやNamedCause.OWNERがあります。説明し辛いので、後述の「状況別のイベントまとめ」を参考にして下さい。NamedCause関係無く「とりあえずプレイヤーが関係した?」というのを取得したいなら以下の書き方ができます

if ( event.getCause().first(Player.class).isPresent() ){
  // Player p = event.getCause().first(Player.class).get();
}

例えば「Player.class」を「Entity.class」などに変えれば、エンダーマンがブロックを持って行ったなどが補足できます。火で燃えることによるブロック焼失などの場合は、原因は「火」ですから以下の様に取得できます。
※焼失の場合は火が原因で焼失するので当然取得できますが、「消火」も「Fire」が「Break」したことになるので、以下の処理で補足できちゃいます。

@Listener
public void onBlockEvent( ChangeBlockEvent.Break event ){
  if ( event.getCause().first(BlockSnapshot.class).isPresent() ){
    BlockSnapshot bs = event.getCause().first(BlockSnapshot.class).get();
    if ( bs.getState().getType().equals( BlockTypes.FIRE ) ){
      // 焼失、あるいは消火
    }
  }
}

Transaction<BlockSnapshot>

前述のCauseは「原因」でしたが、その原因によりブロックが設置されたり、破壊されたりなどの「対象」は以下の通り取得できます。

@Listener
public void onBlockEvent( ChangeBlockEvent.Break event ){
  ~省略~
  BlockSnapshot bs = event.getTransactions().get(0).getOriginal();
  ~省略~
}

Transactionの説明をする前にそもそもBlockSnapshotって?って思うと思いますが、ブロックを扱う場合は少なくともBlockSnapshot、BlockState、BlockTypeを理解しておいた方がいいです。

  • BlockType…「ブロックの種類」だけを扱うオブジェクト。「土(Dirt)」とか「木(Log)」です。「空気(Air)」もあります。
  • BlockState…「ブロックの状態」を扱うオブジェクト。ブロックの種類(BlockType)の他、ブロックの状態も扱えるオブジェクトです。詳しい説明は割愛しますが、Keysなどの細かな情報も保持できます。
  • BlockSnapshot…「実際に設置されている(されていた?)ブロック」の情報です。ですからLocationも持ってます。

つまり、「ブロックを設置した」イベントを処理したい場合、先ずは「BlockSnapshot」を取得します。当然そこから、BlockStateもBlockTypeも取得できます。

さて、本題のTransaction<BlockSnapshot>です。これには大きく2つ(3つ?)の種類があります。

BlockSnapshot bs_o = event.getTransactions().get(0).getOriginal();
BlockSnapshot bs_d = event.getTransactions().get(0).getDefault();
BlockSnapshot bs_f = event.getTransactions().get(0).getFinal();

OriginalとFinalは非常にわかりやすくて「ChangeBlockEvent.Break」イベントの場合は「Original」に破壊される前のブロック情報「Final」は破壊された後のブロック情報が格納されてます。破壊の場合は破壊後のブロックは無しなので、空気(Air)ブロックの情報が格納されます。

Defaultはよくわかりませんが、とりあえずFinalと同じ情報が格納されてるようです。

また、「get(0)」の部分が配列になってますが、これも詳しくは調べてませんが、爆発などの複数のブロックが関与した場合に影響がでるかもしれませんね。通常1つのブロックが関与してるなら「get(0)」だけ処理すれば十分です。

状況別のイベントまとめ

さて、ここからが本題ですが、実はどのイベントでどのCause、あるいはBlockSnapshotが格納されるかの情報がほとんどありません。Block以外のイベントも同様にわかりづらいのですが、とりあえずBlock関連イベントだけ調べてみました。

そもそもBlock関連イベントはとても多いし、1つのアクションで様々なイベントが発生します。例えばブロックを1つ壊すだけで「ChangeBlockEvent.Post→ChangeBlockEvent.Break→NotifyNeighborBlockEvent」という順にイベントが発生します。ですので、ここでは「ChangeBlockEvent.Post」や「NotifyNeighborBlockEvent」は省略します。状況で「ChangeBlockEvent.Pre」も発生しますが、これも様々な状況で発生するので詳細は省略します(説明したところで使いどころが難しいので気にしない方がいいです)。

その他、ブロックに打撃を与えた時に発生する「CollideBlockEvent」も省略してます。

状況 発生イベント Transaction
<BlockSnapshot>
上段:getOriginal
下段:getFinal
getCause
上段:Source
中段:Notifier
下段:Owner
ブロックを設置 ChangeBlockEvent.Place Air
設置後のブロック
実施したEntity
実施したEntity
実施したEntity
ブロックを破壊 ChangeBlockEvent.Break 破壊前のブロック
Air
実施したEntity
実施したEntity
実施したEntity
火打ち石で着火 ChangeBlockEvent.Place Air
Fire
実施したEntity
実施したEntity
実施したEntity
火が燃え広がる
(Entityが着火)
その1
ChangeBlockEvent.Place Air
Fire
BlockState=Fire
着火したEntity
着火したEntity
火が燃え広がる
(Entityが着火)
その2(火の成長)
ChangeBlockEvent.Modify Fire
Fire
BlockState=Fire
着火したEntity
着火したEntity
自然消火
(Entityが着火)
ChangeBlockEvent.Break Fire
Air
BlockState=Fire
–
–
発火によるブロック焼失 ChangeBlockEvent.Place 焼失前ブロック
Fire
BlockState=Fire
着火したEntity
–
苗木が成長その1
※まだ木になってない
(Entityが植えた苗木)
ChangeBlockEvent.Modify sapling
sapling
BlockState=spling
植えたEntity
植えたEntity
苗木が成長その2
※木に変化
(Entityが植えた苗木)
ChangeBlockEvent.Break sapling
log
BlockState=spling
植えたEntity
植えたEntity
木の葉ブロックが消える
※自然消失
※Entityが植えた木も自然の木も同様
ChangeBlockEvent.Decay leaves
air
BlockState=leaves
–
–

以下、補足

  • BlockSnapshotの所に記載のブロック名はBlockTypeですが、実際にStringで取得すると「Minecraft:Air」などになります。
  • 「実施したEntity」などのEntityはMobやPlayerの意味だと捉えて下さい。
  • 「火が燃え広がる」については、実際に何も無い所に火が付くと「その1」が発生します。内部的には火の成長のようなデータを持ってて、見た目は変わらないけど内部的に火が成長したら「その2」が発生します。つまり「その2」イベントは発生はしますが、見た目は変わりません。
    「苗木が成長」も同様で「その1」は内部的な成長です。(成長度合いはBlockSnapshotのプロパティで取得できると思います)

なお、特に書いてないですが、「バケツから水を流す」でも「ChangeBlockEvent.Place」は発生しますが、その後の水の流れは「ChangeBlockEvent.Place」ではなく「ChangeBlockEvent.Pre」です。この辺はちゃんと調べてないので書いてません。

こんな感じで、イベントはそこそこ想像はできますが、Causeの中身やTransaction<BlockSnapshot>の中身は状況が複雑になる程、想像が難しいのでこんな感じで検証してみないと扱うのはとても厳しい感じですね。

ちなみにCauseについてはAPI6で少し見直しがかかるような公式の記載がありましたので、もしかしたら今後変更されるかもなので、注意が必要です。

コメントを残す

これらのHTMLタグが利用可能です

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

  

  

  

Time limit is exhausted. Please reload CAPTCHA.

A sample text widget

Etiam pulvinar consectetur dolor sed malesuada. Ut convallis euismod dolor nec pretium. Nunc ut tristique massa.

Nam sodales mi vitae dolor ullamcorper et vulputate enim accumsan. Morbi orci magna, tincidunt vitae molestie nec, molestie at mi. Nulla nulla lorem, suscipit in posuere in, interdum non magna.