diff --git a/tests/testSpawning.cpp b/tests/testSpawning.cpp index bb4bc1c..d976463 100644 --- a/tests/testSpawning.cpp +++ b/tests/testSpawning.cpp @@ -156,3 +156,161 @@ VF_TEST(aNocturnalRuleIsRefusedInBroadDaylight) { SpawnDenial::WrongTimeOfDay); } +VF_TEST(theWrongBlockUnderfootIsRefused) { + World world = plateWorld(Block::Grass); + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + + SpawnRule rule = zombieRule(); + rule.groundBlocks = {Block::Sand}; + CHECK(checkSpawnRule(rule, env, originCell(), MobCap{}) == SpawnDenial::UnsuitableGround); + + rule.groundBlocks = {Block::Sand, Block::Grass}; + CHECK(checkSpawnRule(rule, env, originCell(), MobCap{}) == SpawnDenial::None); +} + +VF_TEST(aCeilingPressedOntoTheSpawnCellIsRefusedForNoHeadroom) { + World world = plateWorld(Block::Grass); + world.setBlock(0, spawnY + 1, 0, Block::Stone); + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + + CHECK(checkSpawnRule(zombieRule(), env, originCell(), MobCap{}) == SpawnDenial::NoHeadroom); + + SpawnRule crawler = zombieRule(); + crawler.requiredHeadroom = 1; + CHECK(checkSpawnRule(crawler, env, originCell(), MobCap{}) == SpawnDenial::None); +} + +VF_TEST(aTorchLitCellIsRefusedForTooMuchBlockLight) { + World world = plateWorld(Block::Grass); + world.setBlock(1, spawnY, 1, Block::Glowstone); + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + + CHECK(env.blockLightAt(originCell()) > zombieRule().maxBlockLight); + CHECK(checkSpawnRule(zombieRule(), env, originCell(), MobCap{}) == + SpawnDenial::BlockLightTooHigh); + + // The same cell far from the emitter is dark enough again. + Vec3i faraway{30, spawnY, 30}; + CHECK_EQ(env.blockLightAt(faraway), 0); + CHECK(checkSpawnRule(zombieRule(), env, faraway, MobCap{}) == SpawnDenial::None); +} + +VF_TEST(anOpenSkyAtNoonIsRefusedForTooMuchSkyLight) { + World world = plateWorld(Block::Grass); + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + env.daytime = true; + + SpawnRule daywalker = zombieRule(); + daywalker.dayAllowed = true; + CHECK_EQ(env.skyLightAt(originCell()), 15); + CHECK(checkSpawnRule(daywalker, env, originCell(), MobCap{}) == + SpawnDenial::SkyLightTooHigh); +} + +VF_TEST(aSunlessCellIsRefusedForTooLittleLight) { + World world = plateWorld(Block::Grass); + for (int x = -fieldSpan; x <= fieldSpan; ++x) { + for (int z = -fieldSpan; z <= fieldSpan; ++z) world.setBlock(x, floorY + 4, z, Block::Stone); + } + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + env.daytime = true; + env.playerPosition = {20.5f, static_cast(spawnY), 0.5f}; + + CHECK_EQ(env.skyLightAt(originCell()), 0); + CHECK(checkSpawnRule(sheepRule(), env, originCell(), MobCap{}) == SpawnDenial::LightTooLow); +} + +VF_TEST(theSpawnRingAroundThePlayerIsRefusedOnBothSides) { + World world = plateWorld(Block::Grass); + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + + env.playerPosition = {0.5f, static_cast(spawnY), 0.5f}; + CHECK(checkSpawnRule(zombieRule(), env, originCell(), MobCap{}) == + SpawnDenial::TooCloseToPlayer); + + env.playerPosition = {400.5f, static_cast(spawnY), 0.5f}; + CHECK(checkSpawnRule(zombieRule(), env, originCell(), MobCap{}) == + SpawnDenial::TooFarFromPlayer); +} + +VF_TEST(aColumnWithNoFloorReportsThatNoCellWasStandable) { + World world; + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + + Vec3i cell; + CHECK(!pickSpawnCell(env, Vec3i{0, 0, 0}, 99u, 0, cell)); + + Spawner spawner; + SpawnResult result = spawner.runColumn(env, SpawnCategory::Hostile, Vec3i{0, 0, 0}, 99u, 0); + CHECK(result.denial == SpawnDenial::NoStandableCell); + CHECK(!result.spawned); + CHECK_EQ(spawner.denialCount(SpawnDenial::NoStandableCell), 1); +} + +VF_TEST(aCategoryWithNoRulesReportsThatNoRuleMatched) { + World world = plateWorld(Block::Grass); + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + + Spawner spawner; + SpawnResult result = spawner.runColumn(env, SpawnCategory::Water, Vec3i{0, 0, 0}, 7u, 0); + CHECK(result.denial == SpawnDenial::NoRuleMatched); + CHECK_EQ(spawner.denialCount(SpawnDenial::NoRuleMatched), 1); + + // Adding one water rule is all it takes for the category to come alive. + std::vector rules = defaultSpawnRules(); + SpawnRule diver = sheepRule(); + diver.category = SpawnCategory::Water; + rules.push_back(diver); + Spawner wet(rules); + CHECK(wet.pickRule(env, SpawnCategory::Water, 3u) != nullptr); +} + +VF_TEST(theMobCapGrowsWithTheNumberOfLoadedChunks) { + MobCap cap; + CHECK_EQ(cap.capFor(SpawnCategory::Hostile, 289), cap.baseCapFor(SpawnCategory::Hostile)); + CHECK_EQ(cap.capFor(SpawnCategory::Hostile, 578), cap.baseCapFor(SpawnCategory::Hostile) * 2); + CHECK_EQ(cap.capFor(SpawnCategory::Hostile, 0), 0); + + int previous = -1; + for (int chunks = 0; chunks <= 1156; chunks += 17) { + int current = cap.capFor(SpawnCategory::Hostile, chunks); + CHECK(current >= previous); + previous = current; + } + CHECK(cap.capFor(SpawnCategory::Hostile, 1156) > cap.capFor(SpawnCategory::Hostile, 289)); + CHECK(cap.capFor(SpawnCategory::Passive, 289) < cap.capFor(SpawnCategory::Hostile, 289)); + + // A quarter of the world loaded holds roughly a quarter of the mobs. + CHECK_EQ(cap.capFor(SpawnCategory::Hostile, 72), 17); +} + +VF_TEST(aRunningCycleStopsSpawningOnceTheCapIsReached) { + World world = plateWorld(Block::Grass); + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + env.loadedChunks = 17; + env.playerPosition = {0.5f, static_cast(spawnY), 0.5f}; + + MobCap cap; + int limit = cap.capFor(SpawnCategory::Hostile, env.loadedChunks); + CHECK(limit > 0); + + std::vector columns; + for (int cx = -2; cx <= 2; ++cx) { + for (int cz = -2; cz <= 2; ++cz) columns.push_back(Vec3i{cx, 0, cz}); + } + + Spawner spawner; + spawner.setAttemptsPerColumn(6); + for (int cycle = 0; cycle < 12; ++cycle) { + spawner.runCycle(env, SpawnCategory::Hostile, columns, 1234u + static_cast(cycle)); + } +