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で少し見直しがかかるような公式の記載がありましたので、もしかしたら今後変更されるかもなので、注意が必要です。
コメントを残す