環境
PaperAPI : 1.20.4-R0.1
完成物イメージ
手順
CreatureSpawner を取得する
まず、全ての設定をのせるスポナーを座標などから取得します。
スポナーは、機能的ブロックのインタフェースである BlockState
の実装クラスである CreatureSpawner
クラスなのでキャスト等を用いて取得します。
// World型 world: スポナーが存在するワールド
// Location型 location: スポナーの座標を示す Location
Block block = world.getBlockAt(location);
CreatureSpawner spawner = (CreatureSpawner) block.getBlockState();
FallingBlock を作成する
スポーンさせるためのエンティティを作成します。
ここで必要な Entity 型は、実際にゲーム内にスポーンさせないとうまく掴めないため、一度ブロックデータを何も設定しない状態でスポーンさせます。
Entity entity = world.spawn(location, EntityType.FALLING_BLOCK.getEntityClass());
FallingBlock fallingBlock = (FallingBlock) entity;
World#createEntity
を用いるとワールドにエンティティをスポーンさせずに掴むことができます。
しかしこの方法でスポーンさせたエンティティは、ワールドをアンロードした後に操作しようとするとメモリリークを起こすため、私としては World#createEntity()
の使用を推奨しません。(このメソッドが非推奨にされているわけではなく、公式も「メモリリークを起こす危険性があるから扱いには気をつけてね」と言っているだけなので)
Note: The created entity keeps a reference to the world it was created in, care should be taken that the entity does not outlive the world instance as this will lead to memory leaks.
PaperMC 1.20.4-R0.1 JavaDoc RegionAccessor#createEntity() より
おそらく、ワールドをアンロードする Bukkit#unloadWorld()
や Server#unloadWorld()
が対象のワールドのインスタンスを finalize して強制的に破棄 (を GC へ要求) するため、エンティティからの参照だけが残り続けて結果的にメモリリークが発生する、というものだと考えられます。
ブロック種別を設定する
上記の手順で作成した FallingBlock を湧かせようとスポナーに登録しても空気ブロックを湧かせてしまうので、ブロック種別を変更します。
もともと持っている BlockState をコピーして取得した新しい BlockState に対して setType を実行し、ブロックの種別を変更します。
そして、コピーしたものを FallingBlock#setBlockState()
で落下ブロックに設定し、既存の設定を上書きします。
BlockState copied = fallingBlock.getBlockState().copy();
copied.setType(Material.DIAMOND_BLOCK);
fallingBlock.setBlockState(copied);
作成した FallingBlock をスポナーに登録する
spawner.setSpawnedEntity(fallingBlock.createSnapshot());
(この setSpawnedEntity
という関数は引数に EntitySnapshot しか受け付けないのにも拘らず、Entity から EntitySnapshot を作成する唯一の手段である Entity#createEntitySnapshot()
が実験的なメソッドであり、動作の保証をしないことを示すアノテーションをつけられているというこのチグハグさはどうにかしてほしいところです。)
データの更新を適用する
BlockState
の実装クラスはすべて update()
を実行しないことにはデータの更新が行われません。
忘れずに実行しましょう。
spawner.update();