#include "testHarness.h" #include "vf/NetherGen.h" #include #include using namespace vf; namespace { std::map censusColumn(const World& world, int columnX, int columnZ, int ceilingY) { std::map tally; int ox = columnX * chunkSize; int oz = columnZ * chunkSize; for (int gx = ox; gx < ox + chunkSize; ++gx) for (int gz = oz; gz < oz + chunkSize; ++gz) for (int gy = 0; gy < ceilingY; ++gy) ++tally[world.blockAt(gx, gy, gz)]; return tally; } int countOf(const std::map& tally, Block block) { auto it = tally.find(block); return it == tally.end() ? 0 : it->second; } int findColumnWithBiome(const NetherGenerator& generator, NetherBiome biome, int& outColumnX, int& outColumnZ) { for (int columnX = -40; columnX <= 40; ++columnX) { for (int columnZ = -40; columnZ <= 40; ++columnZ) { int probeX = columnX * chunkSize + 8; int probeZ = columnZ * chunkSize + 8; if (generator.biomeAt(probeX, probeZ) != biome) continue; outColumnX = columnX; outColumnZ = columnZ; return 1; } } return 0; } } // namespace VF_TEST(netherChunkGenerationRepeatsExactlyOnTheSameSeed) { NetherGenerator generator; Chunk first, second; generator.generateChunk(first, {2, 3, -4}); generator.generateChunk(second, {2, 3, -4}); CHECK(first.raw() == second.raw()); Chunk elsewhere; generator.generateChunk(elsewhere, {2, 3, 5}); CHECK(first.raw() != elsewhere.raw()); NetherGenerator::Params other; other.seed = 9001u; NetherGenerator reseeded(other); World original, alternative; generator.generateColumn(original, 2, -4); reseeded.generateColumn(alternative, 2, -4); int differingChunks = 0; for (const auto& entry : original.allChunks()) { const Chunk* twin = alternative.chunkAt(entry.first); if (!twin || twin->raw() != entry.second->raw()) ++differingChunks; } CHECK(differingChunks > 0); } VF_TEST(netherColumnIsByteIdenticalAcrossTwoRuns) { NetherGenerator generator; World first, second; generator.generateColumn(first, -3, 6); generator.generateColumn(second, -3, 6); CHECK_EQ(first.chunkCount(), second.chunkCount()); CHECK_EQ(first.chunkCount(), static_cast(generator.stackChunkCount())); int mismatches = 0; for (const auto& entry : first.allChunks()) { const Chunk* twin = second.chunkAt(entry.first); if (!twin || twin->raw() != entry.second->raw()) ++mismatches; } CHECK_EQ(mismatches, 0); } VF_TEST(netherGenerationStaysInsideTheColumnItWasAskedFor) { NetherGenerator generator; World world; generator.generateColumn(world, 5, 7); int strays = 0; for (const auto& entry : world.allChunks()) if (entry.first.x != 5 || entry.first.z != 7) ++strays; CHECK_EQ(strays, 0); } VF_TEST(netherIsSealedByBedrockAtTheFloorAndTheRoof) { NetherGenerator generator; const NetherGenerator::Params& params = generator.settings(); World world; generator.generateColumn(world, 0, 0); int floorGaps = 0; int roofGaps = 0; for (int gx = 0; gx < chunkSize; ++gx) { for (int gz = 0; gz < chunkSize; ++gz) { if (world.blockAt(gx, params.floorY, gz) != Block::Bedrock) ++floorGaps; if (world.blockAt(gx, params.ceilingY - 1, gz) != Block::Bedrock) ++roofGaps; } } CHECK_EQ(floorGaps, 0); CHECK_EQ(roofGaps, 0); // The bands just inside those layers are ragged, not uniformly bedrock. int raggedFloor = 0; for (int gx = 0; gx < chunkSize; ++gx) for (int gz = 0; gz < chunkSize; ++gz) if (generator.isBedrock(gx, params.floorY + 2, gz)) ++raggedFloor; CHECK(raggedFloor > 0); CHECK(raggedFloor < chunkSize * chunkSize); CHECK(!generator.isBedrock(0, 60, 0)); } VF_TEST(netherCavernSitsBetweenTheFloorAndTheRoofMass) { NetherGenerator generator; const NetherGenerator::Params& params = generator.settings(); int drownedColumns = 0; int dryColumns = 0; for (int gx = -400; gx <= 400; gx += 37) { for (int gz = -400; gz <= 400; gz += 37) { NetherColumn column = generator.columnAt(gx, gz); CHECK(column.floorHeight > params.floorY); CHECK(column.ceilingHeight < params.ceilingY - 1); CHECK(column.cavernHeight() >= 9); CHECK_EQ(column.lavaLevel, params.lavaLevel); if (column.drowned) ++drownedColumns; else ++dryColumns; } } CHECK(drownedColumns > 0); CHECK(dryColumns > 0); } VF_TEST(netherLavaSeaLeavesNoAirBelowItsSurface) { NetherGenerator generator; const NetherGenerator::Params& params = generator.settings(); int airBelowLava = 0; int lavaSamples = 0; for (int gx = -120; gx <= 120; gx += 11) { for (int gz = -120; gz <= 120; gz += 11) { NetherColumn column = generator.columnAt(gx, gz); for (int gy = params.floorY + 1; gy <= params.lavaLevel; ++gy) { Block block = generator.blockAt(column, gx, gy, gz); if (block == Block::Air) ++airBelowLava; if (block == Block::Lava) ++lavaSamples; } } } CHECK_EQ(airBelowLava, 0); CHECK(lavaSamples > 0); } VF_TEST(netherCarriesAllFourBiomes) { NetherGenerator generator; std::set seen; for (int gx = -900; gx <= 900; gx += 17) for (int gz = -900; gz <= 900; gz += 17) seen.insert(static_cast(generator.biomeAt(gx, gz))); CHECK_EQ(seen.size(), static_cast(NetherBiome::Count)); } VF_TEST(netherBiomesLayDownTheirOwnSurfaceBlocks) { NetherGenerator generator; int soulSandTops = 0; int basaltTops = 0; int mossTops = 0; int foreignSoulTops = 0; for (int gx = -600; gx <= 600; gx += 13) { for (int gz = -600; gz <= 600; gz += 13) { NetherColumn column = generator.columnAt(gx, gz); Block top = generator.blockAt(column, gx, column.floorHeight, gz); switch (column.biome) { case NetherBiome::SoulSandValley: if (top == Block::SoulSand) ++soulSandTops; if (top == Block::Basalt || top == Block::Moss) ++foreignSoulTops; break; case NetherBiome::BasaltDeltas: if (top == Block::Basalt) ++basaltTops; break; case NetherBiome::CrimsonForest: if (top == Block::Moss) ++mossTops; break; default: break; } } } CHECK(soulSandTops > 0); CHECK(basaltTops > 0); CHECK(mossTops > 0); CHECK_EQ(foreignSoulTops, 0); } VF_TEST(netherSoulSandValleyFillsItselfWithSoulSoil) { NetherGenerator generator; int columnX = 0, columnZ = 0; CHECK(findColumnWithBiome(generator, NetherBiome::SoulSandValley, columnX, columnZ) == 1); World world; generator.generateColumn(world, columnX, columnZ); std::map tally = censusColumn(world, columnX, columnZ, generator.settings().ceilingY); CHECK(countOf(tally, Block::SoulSand) > 0); CHECK(countOf(tally, Block::SoulSoil) > 0); CHECK_EQ(countOf(tally, Block::Basalt), 0); } VF_TEST(netherStocksQuartzGlowstoneAndLava) { NetherGenerator generator; World world; for (int columnX = -2; columnX <= 2; ++columnX) for (int columnZ = -2; columnZ <= 2; ++columnZ) generator.generateColumn(world, columnX, columnZ); int quartz = 0, glowstone = 0, lava = 0, netherrack = 0, magma = 0; for (int gx = -32; gx < 48; ++gx) { for (int gz = -32; gz < 48; ++gz) { for (int gy = 0; gy < generator.settings().ceilingY; ++gy) { switch (world.blockAt(gx, gy, gz)) { case Block::NetherQuartzOre: ++quartz; break; case Block::Glowstone: ++glowstone; break; case Block::Lava: ++lava; break; case Block::Netherrack: ++netherrack; break; case Block::Magma: ++magma; break; default: break; } } } } CHECK(quartz > 0); CHECK(glowstone > 0); CHECK(magma > 0); CHECK(lava > 1000); CHECK(netherrack > quartz); } VF_TEST(netherGlowstoneClingsToTheRoofNotTheFloor) { NetherGenerator generator; World world; for (int columnX = -1; columnX <= 1; ++columnX) for (int columnZ = -1; columnZ <= 1; ++columnZ) generator.generateColumn(world, columnX, columnZ); int lowGlowstone = 0; int highGlowstone = 0; for (int gx = -16; gx < 32; ++gx) { for (int gz = -16; gz < 32; ++gz) { NetherColumn column = generator.columnAt(gx, gz); for (int gy = 0; gy < generator.settings().ceilingY; ++gy) { if (world.blockAt(gx, gy, gz) != Block::Glowstone) continue; if (gy >= column.ceilingHeight - 4) ++highGlowstone; else ++lowGlowstone; } } } CHECK(highGlowstone > 0); CHECK_EQ(lowGlowstone, 0); } VF_TEST(netherCavesCarveRockWithoutEmptyingIt) { NetherGenerator generator; int carved = 0; int total = 0; for (int gx = -200; gx <= 200; gx += 7) { for (int gz = -200; gz <= 200; gz += 7) { for (int gy = 8; gy < 120; gy += 9) { if (generator.isCarved(gx, gy, gz)) ++carved; ++total; } } } CHECK(carved > 0); CHECK(carved < total / 4); } VF_TEST(netherDecorationCanBeSwitchedOffWithoutChangingTheRock) { NetherGenerator::Params bare; bare.decorate = false; NetherGenerator plain(bare); NetherGenerator decorated; World plainWorld, decoratedWorld; plain.generateColumn(plainWorld, 1, 1); decorated.generateColumn(decoratedWorld, 1, 1); int differences = 0; int netherrackKept = 0; for (int gx = 16; gx < 32; ++gx) { for (int gz = 16; gz < 32; ++gz) { for (int gy = 0; gy < 128; ++gy) { Block bareBlock = plainWorld.blockAt(gx, gy, gz); if (bareBlock != decoratedWorld.blockAt(gx, gy, gz)) ++differences; else if (bareBlock == Block::Netherrack) ++netherrackKept; } } } CHECK(differences > 0); CHECK(netherrackKept > 1000); }