Minecraft 1.12.2　ウィザースケルトンのスポーン条件


ほぼマイクラブログに近いけどこんなのQiitaに書いていいのか謎

# 環境

- Minecraft 1.12.2 Java Edition
- forgeSrc-1.12.2-14.23.4.2705

# 問題

ここではネザー要塞でウィザースケルトンがスポーンできる座標がどのように決定されているかを調べたい。それが分かるとウィザースケルトンTTの湧き層を作る時の戦略に良いからだ。

ここでは明るさや障害物は考慮しない。飽くまでその座標に**湧き層を作ったときに**湧けるかどうかを考える。

# 関連記事

- [1] ウィザースケルトントラップの作り方 湧き層 編 #366 - オロオロKTのマイクラブログ http://blog.minecraft-oroorokt.com/entry/2017/11/06/%E3%82%A6%E3%82%A3%E3%82%B6%E3%83%BC%E3%82%B9%E3%82%B1%E3%83%AB%E3%83%88%E3%83%B3%E3%83%88%E3%83%A9%E3%83%83%E3%83%97%E3%81%AE%E4%BD%9C%E3%82%8A%E6%96%B9_%E6%B9%A7%E3%81%8D%E5%B1%A4_%E7%B7%A8_%2336

ウィザースケルトントラップを作っている記事。なんでも`十字路が２つ続いている場所が敵mobが湧きやすい`らしい。

この記事では橋と橋の間の何も無い空間にも湧き層を作っているがちゃんと湧くのだろうか。

# 調査

## ウィザースケルトンの呼び出し

ウィザースケルトンのクラスは`net.minecraft.entity.monster.EntityWitherSkeleton`。

----

ネザー要塞`net.minecraft.world.gen.structure.MapGenNetherBridge`には、湧くリストにウィザースケルトンを追加している記述がある。

```java:net.minecraft.world.gen.structure.MapGenNetherBridge.MapGenNetherBridge
this.spawnList.add(new Biome.SpawnListEntry(EntityBlaze.class, 10, 2, 3));
this.spawnList.add(new Biome.SpawnListEntry(EntityPigZombie.class, 5, 4, 4));
this.spawnList.add(new Biome.SpawnListEntry(EntityWitherSkeleton.class, 8, 5, 5));
this.spawnList.add(new Biome.SpawnListEntry(EntitySkeleton.class, 2, 5, 5));
this.spawnList.add(new Biome.SpawnListEntry(EntityMagmaCube.class, 3, 4, 4));
```

見た感じ28.5%の確率で5体のウィザースケルトンが湧くように見えるが実際は不明。とりあえず湧く確率や効率ではなく湧き条件が知りたいのでそのままにしておく（なお、ガスト・ゾンビピッグマン・マグマキューブ・エンダーマンはネザーに通常湧く可能性があると `net.minecraft.world.biome.BiomeHell` に書いてあるが、要塞用のスポーンリストとバイオームによる通常のスポーンリストのどちらを採用するかはディメンションが決定しており、要塞内判定を貰うと後者が無視されるので多分ウィザースケルトン用の湧き場にはエンダーマンは湧かない）。

そのリストは`List<SpawnListEntry> net.minecraft.world.gen.structure.MapGenNetherBridge.getSpawnList()`で取得できる。

そしてそれが唯一利用されているのは`List<SpawnListEntry> net.minecraft.world.gen.ChunkGeneratorHell.getPossibleCreatures(EnumCreatureType creatureType, BlockPos pos)`である。

一般に生物（`EntityLiving`）の湧き条件はディメンション（`IChunkGenerator`）が与えている。

## ネザーにおける、ある座標でスポーン可能な生物

で、`ChunkGeneratorHell.getPossibleCreatures`の中身は大体次のようになっている。

```java:
List<Biome.SpawnListEntry> getPossibleCreatures(EnumCreatureType creatureType, BlockPos pos)
{
    if (creatureType == 敵) {
        if (isInsideStructure(pos)) {
            return ネザー要塞のスポーンリスト;
        }
        if (isPositionInStructure(world, pos) && posの1個下がネザーレンガ) {
            return ネザー要塞のスポーンリスト;
        }
    }
    return ネザーの通常のスポーンリスト;
}
```

ここでネザーレンガはBlockが`Blocks.NETHER_BRICK`であることが条件。ネザーレンガのMetadata違いでも反応するが、赤いネザーレンガはBlockが違うのでNG（可だったら建材作るときにネザーラックをケチれたのに）。Blockのinstanceofでもないのでたとえネザーレンガの子クラスでも無理。

## ネザー要塞内か否か

ここで、次の二つは何だろうか？おそらくネザー要塞の内部か否かだが、なんか2種類の判定がある。

- `boolean net.minecraft.world.gen.structure.MapGenStructure.isInsideStructure(BlockPos pos)`
- `boolean net.minecraft.world.gen.structure.MapGenStructure.isPositionInStructure(World worldIn, BlockPos pos)`

この2個はMapGenStructure（構造物）に共通の操作である。村とか廃坑とかもこの2個の操作は持っている。MapGenStructureは構造物ごとではなくディメンションごとに1個存在する。

### MapGenStructure.isInsideStructure

初期化とかを除くと動作は大体こんな感じ。

```java:
boolean isInsideStructure(BlockPos pos)
{
    return getStructureAt(pos) != null;
}

StructureStart getStructureAt(BlockPos pos)
{
    ObjectIterator objectiterator = structureMap.values().iterator();
    label31:
    while (objectiterator.hasNext()) {
        StructureStart structurestart = (StructureStart)objectiterator.next();

        if (structurestart.isSizeableStructure() && structurestart.getBoundingBox().isVecInside(pos)) {

            // (1)
            Iterator<StructureComponent> iterator = structurestart.getComponents().iterator();
            while (true) {
                if (!iterator.hasNext()) continue label31;
                StructureComponent structurecomponent = iterator.next();
                if (structurecomponent.getBoundingBox().isVecInside(pos)) break;
            }
            return structurestart;

        }

    }
    return null;
}
```

指定座標にある構造物（ネザーではネザー要塞）を取得し、それが無でなかった場合にスポーンする。構造物の取得部分では構造物のコンポーネント1個1個についてループしていちいち判定している。

(1)の周りを覚えておこう。

### MapGenStructure.isPositionInStructure

基本的な枠組みは`getStructureAt`と似ている。

```java:
boolean isPositionInStructure(World worldIn, BlockPos pos)
{
    ObjectIterator objectiterator = structureMap.values().iterator();
    while (objectiterator.hasNext()) {
        StructureStart structurestart = (StructureStart)objectiterator.next();

        if (structurestart.isSizeableStructure() && structurestart.getBoundingBox().isVecInside(pos)) {

            // (2)
            return true;

        }

    }
    return false;
}
```

ここで(2)は(1)に対応する部分である。(1)では厳密に各部屋ごとに判定をしていたが、(2)ではそのままtrueを返しているためかなりアバウトな感じになっている。

# 予想

ウィザースケルトンは次の場合のいずれかでスポーンする。

1. ネザー要塞のいずれかの部屋（通路含む）が占める直方体範囲の中
1. ネザー要塞の全体を含む直方体範囲の中のネザーレンガの上

これなら湧き層の床をネザーレンガにすればかなりの湧き数が確保できそうである。

# 検証

`net.minecraft.item.ItemClock.onItemUse`を次のコードで上書きしてウィザースケルトンがスポーンできる範囲を調べた。

```java:
if (worldIn.isRemote) return EnumActionResult.SUCCESS;

for (int x = -40; x <= 40; x++) {
	for (int y = -100; y <= 100; y++) {
		for (int z = -40; z <= 40; z++) {

			BlockPos pos2 = pos.add(x, y, z);
			List<SpawnListEntry> chunkProvider = ((ChunkProviderServer) worldIn.getChunkProvider()).getPossibleCreatures(EnumCreatureType.MONSTER, pos2);
			if (chunkProvider.stream()
				.filter(e -> e.entityClass == EntityWitherSkeleton.class)
				.findAny()
				.isPresent()) {
				worldIn.setBlockState(pos2, Blocks.GLASS.getDefaultState());
			}

		}
	}
}
```

![2018-09-27_05.25.52.png](https://qiita-image-store.s3.amazonaws.com/0/280627/e18fcb25-b23b-4335-997c-ad636341e4c6.png)

ガラスで完全に埋まっている部分は判定1に引っかかって無条件でウィザースケルトンが25%くらいの確率で湧くポジションである。こうしてみると結構狭いようで広いように見えて狭い。

ガラスとネザーレンガで縞模様になっている領域は判定2でウィザースケルトンが湧く場所である。なぜ縞模様になるのかというと、湧く場所を下から順番にガラスにしたので、さっきガラスにした部分は判定2から漏れるためである。判定2では図で縞模様になっている部分以外にも、そのネザー要塞を囲む大きな直方体の中ならどこでも引っかかる。

----

![2018-09-27_05.29.49.png](https://qiita-image-store.s3.amazonaws.com/0/280627/9129c925-bba9-3fa5-9ca6-1b4e2f614682.png)

この画像では縞模様が下の方でネザーレンガ埋めに切り替わっているが、プログラムではその部分もちゃんと判定している。つまりそこはネザー要塞に含まれる座標ではないということ。**ネザー要塞の下側のネザーレンガで埋まっている部分はネザー要塞の一部ではない**。

----

次の画像では左半分だけ置換して右はそのままである。

![2018-09-27_05.53.51.png](https://qiita-image-store.s3.amazonaws.com/0/280627/47e0e1ff-9968-3573-d72f-a0383c51d1a4.png)

関連記事[1]のように橋のY±4に湧き層を作るとちゃんと綺麗に湧くことができる。上が際どいが、多分ガラスの1個下のブロックの上に直立する形で敵が湧く。別の実験でこのような形にした（上層は上付き半ブロックで作った）とき、ちゃんと上層にも湧いているのを見たことがある。

----

![2018-09-27_05.34.29.png](https://qiita-image-store.s3.amazonaws.com/0/280627/d914151a-d04e-5884-221d-fc223a5e7223.png)

どうやらこんな外れたところでも下がネザーレンガであればウィザースケルトンが湧くらしい。本当にこんなところに湧き層作って湧くの？

![2018-09-27_05.38.45.png](https://qiita-image-store.s3.amazonaws.com/0/280627/909e6375-5cf2-5a72-a6fb-ed1c1a13d227.png)

からの**湧いた**。証明終了。

# 結論

ウィザースケルトンは次の場合のいずれかでスポーンする。

1. ネザー要塞のいずれかの部屋（通路含む）が占める直方体範囲の中
1. ネザー要塞の全体を含む直方体範囲の中のネザーレンガの上

**ネザー要塞の下の脚部分は要塞の一部ではない**ので注意。

# 考察

本記事が正しいならば、関連記事[1]では橋の間の空間にも湧き層を作ったうえで`湧き層を作るブロックはmobが湧くブロックならなんでもOK`としているが、実はそこはウィザースケルトンが湧いていないことになる。

湧かない証明は面倒くさいししても得られるものがないのでしない。とりあえず床はネザーレンガにしておけばかなりアバウトでも湧くっぽい。