Minecraft　1.12.2　生成アルゴリズムが変わってもエンダーアイは最も距離が近いエンド要塞を指す

Minecraft攻略日記です。

# 環境

- Minecraft 1.12.2 Java Edition
- forgeSrc-1.12.2-14.23.4.2705

# 背景

Minecraftの地上世界にはエンドへの要塞（Stronghold）が存在する。

- https://minecraft-ja.gamepedia.com/%E8%A6%81%E5%A1%9E

これは世界に限られた箇所にしか存在せず、攻略するにはエンダーアイを投げて探さなければならない。エンダーアイは最寄りのエンド遺跡に向かって飛んでいく。

----

さて、エンド要塞の個数はバージョン1.9の更新時に、以前の3か所から128か所に増えた。そしてそれらの要塞はリング状の領域に特定の個数ずつ配置されているらしい~~電子殻~~。

![](https://gamepedia.cursecdn.com/minecraft_gamepedia/thumb/9/9d/Strongholds_1.9.png/600px-Strongholds_1.9.png?version=a46e05b82e60339442901961cf1be0d8)

で、その中に `The strongholds are generated at roughly equal angles from the center point of the world (for instance, each stronghold in a ring of 3 is in the region of 120 degrees from the others, measured from the origin).` なる文で説明される配置で要塞が存在するという。つまり概ね等間隔ということだ。

---

一方、以下のサイトの図では等間隔ではなく角度もばらばらに存在している。

- ジ・エンドに行くために、エンドポータル（要塞）を簡単に見つけるコツを解説 http://chanhino.com/minecraft-end-portal/

![](http://chanhino.com/wp-content/uploads/2016/08/place.png)

これは**各リングを均等にn個のピースに分割し、各ピースのどこかに要塞がスポーンする**ということなのだろうと、私は勝手に想像している<sup>[要検証]</sup>。

# 疑問

まあそれはそうとして、ここで一つ問題が出てくる。エンダーアイは最寄りの（距離的に最短の）遺跡を指すのか？それとも、投げた座標が所属するピースにある遺跡を指すのか？

処理上は明らかに後者の方が楽そうだ。座標をピース位置に変換して要塞座標にすれば求まる。前者だと少なくとも周辺の要塞を数個知らなければいけない。

なお、英語版Wikiでは`the nearest stronghold`に飛ぶと書いてある。

- Eye of Ender https://minecraft.gamepedia.com/Eye_of_Ender

# 調査

その謎を究明すべく、エンダーアイの右クリック処理（`net.minecraft.item.ItemEnderEye.onItemRightClick`）を調べた。そこでは次のようなコードで座標を取得していた。

```java:
BlockPos blockpos = ((WorldServer)worldIn).getChunkProvider().getNearestStructurePos(worldIn, "Stronghold", new BlockPos(playerIn), false);
```

要塞の座標の計算はアイテムのイベントに書かれているのではなく「最寄りの構造物の座標」を返す関数に要塞を指定して取得するようだ。次のメソッドを経由して目的の場所にたどり着いた。

- `net.minecraft.world.gen.ChunkProviderServer.getNearestStructurePos`
- `net.minecraft.world.gen.ChunkGeneratorOverworld.getNearestStructurePos`

以下が最寄りのエンド要塞の位置を返すメソッド`net.minecraft.world.gen.structure.MapGenStronghold.getNearestStructurePos`及び付属のメソッドである。

```java:
public BlockPos getNearestStructurePos(World worldIn, BlockPos pos, boolean findUnexplored)
{
    if (!this.ranBiomeCheck) {
        this.generatePositions();
        this.ranBiomeCheck = true;
    }

    BlockPos blockpos = null;
    BlockPos.MutableBlockPos blockpos$mutableblockpos = new BlockPos.MutableBlockPos(0, 0, 0);
    double d0 = Double.MAX_VALUE;

    for (ChunkPos chunkpos : this.structureCoords) {
        blockpos$mutableblockpos.setPos((chunkpos.x << 4) + 8, 32, (chunkpos.z << 4) + 8);
        double d1 = blockpos$mutableblockpos.distanceSq(pos);

        if (blockpos == null) {
            blockpos = new BlockPos(blockpos$mutableblockpos);
            d0 = d1;
        } else if (d1 < d0) {
            blockpos = new BlockPos(blockpos$mutableblockpos);
            d0 = d1;
        }
    }

    return blockpos;
}

private void generatePositions()
{
	this.initializeStructureData(this.world);
	int i = 0;
	ObjectIterator lvt_2_1_ = this.structureMap.values().iterator();

	while (lvt_2_1_.hasNext()) {
		StructureStart structurestart = (StructureStart) lvt_2_1_.next();

		if (i < this.structureCoords.length) {
			this.structureCoords[i++] = new ChunkPos(structurestart.getChunkPosX(), structurestart.getChunkPosZ());
		}
	}

	Random random = new Random();
	random.setSeed(this.world.getSeed());
	double d1 = random.nextDouble() * Math.PI * 2.0D;
	int j = 0;
	int k = 0;
	int l = this.structureMap.size();

	if (l < this.structureCoords.length) {
		for (int i1 = 0; i1 < this.structureCoords.length; ++i1) {
			double d0 = 4.0D * this.distance + this.distance * (double) j * 6.0D + (random.nextDouble() - 0.5D) * this.distance * 2.5D;
			int j1 = (int) Math.round(Math.cos(d1) * d0);
			int k1 = (int) Math.round(Math.sin(d1) * d0);
			BlockPos blockpos = this.world.getBiomeProvider().findBiomePosition((j1 << 4) + 8, (k1 << 4) + 8, 112, this.allowedBiomes, random);

			if (blockpos != null) {
				j1 = blockpos.getX() >> 4;
				k1 = blockpos.getZ() >> 4;
			}

			if (i1 >= l) {
				this.structureCoords[i1] = new ChunkPos(j1, k1);
			}

			d1 += (Math.PI * 2D) / (double) this.spread;
			++k;

			if (k == this.spread) {
				++j;
				k = 0;
				this.spread += 2 * this.spread / (j + 1);
				this.spread = Math.min(this.spread, this.structureCoords.length - i1);
				d1 += random.nextDouble() * Math.PI * 2.0D;
			}
		}
	}
}
```

`this.structureCoords`は128要素の配列で、要塞座標列が入っている。未初期化の状態で要塞の座標にアクセスしたときにはじめて計算処理`generatePositions`が行われる。`getNearestStructurePos`を見ると、どう見ても最寄りの要塞を取得しているように見える。

要塞の座標決定部分については今回は行わない（あとでやるかもしれないから資料としてコードを丸上げしておく）。

# 結果

**エンダーアイは、未生成のものも含むワールド中のすべてのエンド要塞の中から最も距離的に近いものを指す**。