解析情報という名のMinecraft攻略記事
環境
- forgeBin-1.12.2-14.23.5.2768.jar
バニラの鉱石生成
見る場所
地上の鉱石生成部分のコード。
void net.minecraft.world.gen.ChunkGeneratorOverworld.populate(int p_populate_1_, int p_populate_2_)
void net.minecraft.world.biome.Biome.decorate(World p_decorate_1_, Random p_decorate_2_, BlockPos p_decorate_3_)
void net.minecraft.world.biome.BiomeDecorator.decorate(World p_decorate_1_, Random p_decorate_2_, Biome p_decorate_3_, BlockPos p_decorate_4_)
void net.minecraft.world.biome.BiomeDecorator.genDecorations(Biome p_genDecorations_1_, World p_genDecorations_2_, Random p_genDecorations_3_)
void net.minecraft.world.biome.BiomeDecorator.generateOres(World p_generateOres_1_, Random p_generateOres_2_)
ネザーの鉱石生成部分のコード。
void net.minecraft.world.gen.ChunkGeneratorHell.populate(int p_populate_1_, int p_populate_2_)
基本はIChunkGenerator.populate
で鉱石が生成される。ネザーは当該メソッドに直接ネザークォーツ生成のコードが置かれているが、地上は地形生成が複雑なのでメソッドを4個呼んだ先で鉱石が生成されている。
鉱石の生成場所算出の基本的なルーチン
例としてネザークォーツを生成するコードを整形して見てみる。
WorldGenerator quartzGen = new WorldGenMinable(
Blocks.QUARTZ_ORE.getDefaultState(), // oreBlock 置換後のブロックを直接指定する
14, // numberOfBlocks 数が大きいほど多くの鉱石が生成されるが、14回生成判定が行われる訳ではない模様
BlockMatcher.forBlock(Blocks.NETHERRACK) // predicate 置換前のブロックを識別する判定機
);
BlockPos blockpos = new BlockPos(chunkX * 16, 0, chunkZ * 16);
if (TerrainGen.generateOre(world, rand, quartzGen, blockpos, OreGenEvent.GenerateMinable.EventType.QUARTZ)) {
for (int i = 0; i < 16; i++) {
quartzGen.generate(world, rand, blockpos.add(
rand.nextInt(16),
rand.nextInt(108) + 10,
rand.nextInt(16)));
}
}
quartzGen
は不変である。ifの中身はネザーにネザークォーツを生成するか否かの判定で、MODがOreGenEvent.GenerateMinable
イベントをキャッチしてresult
にDENY
をセットするとキャンセルされる機構になっている。本質でないのでこの判定部分を割愛するとこうなる。
WorldGenerator quartzGen = new WorldGenMinable(
Blocks.QUARTZ_ORE.getDefaultState(), // oreBlock 置換後のブロックを直接指定する
14, // numberOfBlocks 数が大きいほど多くの鉱石が生成されるが、14回生成判定が行われる訳ではない模様
BlockMatcher.forBlock(Blocks.NETHERRACK) // predicate 置換前のブロックを識別する判定機
);
BlockPos blockpos = new BlockPos(chunkX * 16, 0, chunkZ * 16);
for (int i = 0; i < 16; i++) {
quartzGen.generate(world, rand, blockpos.add(
rand.nextInt(16),
rand.nextInt(108) + 10,
rand.nextInt(16)));
}
rand.nextInt(16)
は0~15の範囲の整数乱数、rand.nextInt(108) + 10
は10~117の範囲の整数乱数を与える。blockpos
はそのチャンクの北西のブロックを表し、X・Z座標はともに16の倍数である。16の倍数に0~15の範囲の整数乱数を加えるので、XZ座標は「そのチャンク内のランダムなどこか」となる。一方Y座標は単に10~117である。それを1チャンク当たり固定で16回試行するので、完全にネザーラックで埋まったチャンクには、XZ座標は均一に、Y座標は10~117を起点とした範囲に、合計16個の、大きさ14のネザークォーツの鉱脈があることになる。
バニラの鉱石生成が記述されている場所
ネザークォーツを生成するWorldGenerator
はWorldGenMinable
であったが、これは地上側でもメソッドの呼び出し過程が違うだけで同じように呼び出されている。
BiomeDecorator#decorate
中のWorldGenMinable
の呼び出し場所は次のとおりである。
dirtGen = new WorldGenMinable(Blocks.DIRT.getDefaultState(), chunkProviderSettings.dirtSize);
gravelOreGen = new WorldGenMinable(Blocks.GRAVEL.getDefaultState(), chunkProviderSettings.gravelSize);
graniteGen = new WorldGenMinable(Blocks.STONE.getDefaultState().withProperty(BlockStone.VARIANT, BlockStone.EnumType.GRANITE), chunkProviderSettings.graniteSize);
dioriteGen = new WorldGenMinable(Blocks.STONE.getDefaultState().withProperty(BlockStone.VARIANT, BlockStone.EnumType.DIORITE), chunkProviderSettings.dioriteSize);
andesiteGen = new WorldGenMinable(Blocks.STONE.getDefaultState().withProperty(BlockStone.VARIANT, BlockStone.EnumType.ANDESITE), chunkProviderSettings.andesiteSize);
coalGen = new WorldGenMinable(Blocks.COAL_ORE.getDefaultState(), chunkProviderSettings.coalSize);
ironGen = new WorldGenMinable(Blocks.IRON_ORE.getDefaultState(), chunkProviderSettings.ironSize);
goldGen = new WorldGenMinable(Blocks.GOLD_ORE.getDefaultState(), chunkProviderSettings.goldSize);
redstoneGen = new WorldGenMinable(Blocks.REDSTONE_ORE.getDefaultState(), chunkProviderSettings.redstoneSize);
diamondGen = new WorldGenMinable(Blocks.DIAMOND_ORE.getDefaultState(), chunkProviderSettings.diamondSize);
lapisGen = new WorldGenMinable(Blocks.LAPIS_ORE.getDefaultState(), chunkProviderSettings.lapisSize);
見ての通り、鉱石系ブロック以外に土や岩石もWorldGenMinable
であることが分かる。
このWorldGenerator
のgenerate
を呼び出すと鉱石が生成されるわけだが、呼び出され方が2種類ある。一つは通常のもので、もう一つはラピスラズリ専用の方法である。
// ラピスラズリ以外(例:ダイヤモンド)
genStandardOre1(world, random, chunkProviderSettings.diamondCount, diamondGen,
chunkProviderSettings.diamondMinHeight,
chunkProviderSettings.diamondMaxHeight);
void genStandardOre1(World world, Random random, int count, WorldGenerator worldGen, int minHeight, int maxHeight)
{
if (maxHeight < minHeight) { // minの方が大きかったら入れ替える
int i = minHeight;
minHeight = maxHeight;
maxHeight = i;
} else if (maxHeight == minHeight) { // min == maxだったら、生成範囲を拡張する
if (minHeight < 255) {
maxHeight++;
} else {
minHeight--;
}
}
// この時点でmin < maxが確定
for (int j = 0; j < count; ++j) {
BlockPos blockpos = chunkPos.add(
random.nextInt(16),
random.nextInt(maxHeight - minHeight) + minHeight,
random.nextInt(16));
worldGen.generate(world, random, blockpos);
}
}
// ラピスラズリ
genStandardOre2(world, random, chunkProviderSettings.lapisCount, lapisGen,
chunkProviderSettings.lapisCenterHeight,
chunkProviderSettings.lapisSpread);
void genStandardOre2(World world, Random random, int count, WorldGenerator worldGen, int centerHeight, int spread)
{
for (int i = 0; i < count; ++i) {
BlockPos blockpos = chunkPos.add(
random.nextInt(16),
random.nextInt(spread) + random.nextInt(spread) + centerHeight - spread,
random.nextInt(16));
worldGen.generate(world, random, blockpos);
}
}
genStandardOre1
の前半を占める引数の値チェック部分を無視すると、両者の違いは生成高度の算出のされ方のみである。ネザークォーツの生成も「ラピスラズリ以外」の生成ルーチンに一般化できる。chunkPos
は、生成対象のチャンクの北西のブロック座標である。
まとめると、鉱石生成のYの算出方法は次のようになる。
-
rand.nextInt(maxHeight - minHeight) + minHeight
: 地上のラピスラズリ以外 -
rand.nextInt(spread) + rand.nextInt(spread) + centerHeight - spread
: ラピスラズリ -
rand.nextInt(108) + 10
: ネザークォーツ
ネザークォーツは、「地上のラピスラズリ以外」でminHeight = 10
,maxHeight = 118
とした場合と同じである。
「地上のラピスラズリ以外」はminHeight
~maxHeight - 1
の範囲の整数乱数である。
一方、ラピスラズリの方は少し特殊である。rand.nextInt(spread) + rand.nextInt(spread)
の部分は、例えばspread = 3
とすると[0 ~ 2] + [0 ~ 2] = [0, 1, 1, 2, 2, 2, 3, 3, 4]
という雰囲気になる(9個の数のうちランダムに得るの意)。最低値が0、最頻値がspread - 1
、最大値が(spread - 1) * 2
の三角型の確率分布である。これにcenterHeight - spread
を加えているので、最低値がcenterHeight - spread
、最頻値がcenterHeight - 1
、最大値がcenterHeight + spread - 2
の三角型の確率分布となる。
centerHeight - 1
を中心として±(spread - 1)
の範囲に分布するとみてよい。
鉱石生成のパラメータ
地上の鉱石生成のパラメータはchunkProviderSettings
に支配されているが、これが供給する値の源流はnet.minecraft.world.gen.ChunkGeneratorSettings.Factory
にある。
まとめると以下のようになる。数値の左の*
は、それがコンフィグで変えられないことを表す。列g
は、1のときに汎用、2のときにラピスラズリ用の座標算出を行う。列g
はコンフィグで変更することはできない。
name g size count minHeight maxHeight 備考
centerHeight spread
dirt 1 33 10 0 256
gravel 1 33 8 0 256
granite 1 33 10 0 80
diorite 1 33 10 0 80
andesite 1 33 10 0 80
coal 1 17 20 0 128
iron 1 9 20 0 64
gold 1 9 2 0 32
gold 1 9 *20 *32 *80 メサのみ
redstone 1 8 8 0 16
diamond 1 8 1 0 16
lapis 2 7 1 16 16
netherQuartz 1 *14 *16 *10 *118 ネザーのみ
silverfish 1 *9 *7 *0 *64 高山のみ
メサの金鉱石はnet.minecraft.world.biome.BiomeMesa.Decorator.generateOres
にて、共用のgoldGen
を流用する形で生成している。そのため設定でサイズを変更すると影響を受ける(と思う)。高山の銀魚はnet.minecraft.world.biome.BiomeHills
を参照。
高山のエメラルドはnet.minecraft.world.biome.BiomeHills.EmeraldGenerator
であり、実はWorldGenMinable
ではない。エメラルドの生成回数や座標などはすべて同クラスが支配している。エメラルドは特殊な生成ルーチンであるため、今回は扱わない。
シルバーフィッシュスポーナーの謎
現バージョンのMinecraft Forgeの高山のシルバーフィッシュスポーナーの生成コードを見てみると、一つの謎がある。
for (int j1 = 0; j1 < 7; ++j1) {
int k1 = p_decorate_2_.nextInt(16);
int l1 = p_decorate_2_.nextInt(64);
int i2 = p_decorate_2_.nextInt(16);
if (!TerrainGen.generateOre(world, random, silverfishSpawner, chunkPos.add(j1, k1, l1), OreGenEvent.GenerateMinable.EventType.SILVERFISH)) {
continue;
}
silverfishSpawner.generate(world, random, chunkPos.add(k1, l1, i2));
}
生成基点座標j1, k1, l1
は、何回目の試行か, X座標加算値, Y座標加算値
となるので生成可否判定がおかしな座標で行われてしまう。実際に生成される場所は違和感のない記述である。
鉱石の追加方法
地上
MODで地上の鉱石を追加するには、MinecraftForge.ORE_GEN_BUS
に対してOreGenEvent.Pre
またはOreGenEvent.Post
をフックすればよい。Miragium
という鉱石を追加するサンプルを載せる。これはFMLPreInitializationEvent
の末尾にでも呼び出せばよい。
MinecraftForge.ORE_GEN_BUS.register(new Object() {
private WorldGenMinable worldGenMiragium = new WorldGenMinable(blockOre.getState(BlockOre.EnumType.MIRAGIUM), 4);
@SubscribeEvent
public void accept(OreGenEvent.Post event)
{
genStandardOre(event, 16, worldGenMiragium, 64, 255);
}
private void genStandardOre(OreGenEvent.Post event, int count, WorldGenerator generator, int minHeightInclusive, int maxHeightExclusive)
{
for (int j = 0; j < count; ++j) {
generator.generate(event.getWorld(), event.getRand(), event.getPos().add(
event.getRand().nextInt(16),
event.getRand().nextInt(maxHeightExclusive - minHeightInclusive) + minHeightInclusive,
event.getRand().nextInt(16)));
}
}
});
地上以外
今のところ未調査。実は地上用のコードがネザーの地形生成にも侵食しているかもしれない(しかし置換元となる焼き石が存在しないので実害がない(気づけない))。