diff --git a/tests/testSpawning.cpp b/tests/testSpawning.cpp new file mode 100644 index 0000000..bb4bc1c --- /dev/null +++ b/tests/testSpawning.cpp @@ -0,0 +1,158 @@ +#include "testHarness.h" +#include "vf/Spawning.h" + +#include +#include + +using namespace vf; + +namespace { + +constexpr int floorY = 64; +constexpr int spawnY = 65; +constexpr int fieldSpan = 48; + +// One flat plate of `surface` with the sky wide open above it, big enough for +// the light box below to see nothing but that plate. +World plateWorld(Block surface) { + World world; + for (int x = -fieldSpan; x <= fieldSpan; ++x) { + for (int z = -fieldSpan; z <= fieldSpan; ++z) world.setBlock(x, floorY, z, surface); + } + return world; +} + +LightField bakeLight(const World& world) { + return computeLight(world, Vec3i{-fieldSpan, floorY - 2, -fieldSpan}, + Vec3i{fieldSpan * 2 + 1, 10, fieldSpan * 2 + 1}); +} + +SpawnEnvironment nightEnvironment(const World& world, const LightField& light) { + SpawnEnvironment env; + env.world = &world; + env.light = &light; + env.dimension = DimensionId::Overworld; + env.fallbackBiome = BiomeKind::Plains; + env.daytime = false; + env.playerPresent = true; + env.playerPosition = {40.5f, static_cast(spawnY), 0.5f}; + env.loadedChunks = 289; + env.topY = 70; + env.bottomY = 61; + return env; +} + +SpawnRule zombieRule() { + std::vector rules = defaultSpawnRules(); + return *findSpawnRule(rules, Species::Zombie); +} + +SpawnRule sheepRule() { + std::vector rules = defaultSpawnRules(); + return *findSpawnRule(rules, Species::Sheep); +} + +Vec3i originCell() { return Vec3i{0, spawnY, 0}; } + +} // namespace + +VF_TEST(theStockRuleTableCoversEverySpeciesWithACoherentCategory) { + std::vector rules = defaultSpawnRules(); + CHECK_EQ(rules.size(), 6u); + + for (int index = 0; index < static_cast(Species::Count); ++index) { + Species species = static_cast(index); + const SpawnRule* rule = findSpawnRule(rules, species); + CHECK(rule != nullptr); + if (rule == nullptr) continue; + CHECK(rule->category == categoryForSpecies(species)); + CHECK(rule->packMin >= 1); + CHECK(rule->packMax >= rule->packMin); + CHECK(rule->weight > 0); + } + + CHECK(findSpawnRule(rules, Species::Zombie)->dayAllowed == false); + CHECK(findSpawnRule(rules, Species::Sheep)->dayAllowed); + CHECK(std::string(spawnCategoryName(SpawnCategory::Water)) == std::string("water")); + CHECK(std::string(spawnCategoryName(SpawnCategory::Ambient)) == std::string("ambient")); + + for (int index = 0; index < spawnDenialCount; ++index) { + SpawnDenial denial = static_cast(index); + CHECK(std::string(spawnDenialName(denial)) != std::string("unknown")); + } +} + +VF_TEST(aCellThatSatisfiesEveryConditionIsAccepted) { + World world = plateWorld(Block::Grass); + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + MobCap cap; + + CHECK_EQ(env.skyLightAt(originCell()), 4); + CHECK_EQ(env.blockLightAt(originCell()), 0); + CHECK(checkSpawnRule(zombieRule(), env, originCell(), cap) == SpawnDenial::None); +} + +VF_TEST(aFullCategoryCapRefusesTheSpawnBeforeAnythingElseIsChecked) { + World world = plateWorld(Block::Grass); + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + MobCap cap; + + env.population[static_cast(SpawnCategory::Hostile)] = + cap.capFor(SpawnCategory::Hostile, env.loadedChunks); + CHECK(checkSpawnRule(zombieRule(), env, originCell(), cap) == + SpawnDenial::CategoryCapReached); + + env.population[static_cast(SpawnCategory::Hostile)] -= 1; + CHECK(checkSpawnRule(zombieRule(), env, originCell(), cap) == SpawnDenial::None); +} + +VF_TEST(aRuleFromAnotherDimensionIsRefused) { + World world = plateWorld(Block::Grass); + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + env.dimension = DimensionId::Nether; + CHECK(checkSpawnRule(zombieRule(), env, originCell(), MobCap{}) == + SpawnDenial::WrongDimension); +} + +VF_TEST(aCellOutsideTheRulesHeightBandIsRefused) { + World world = plateWorld(Block::Grass); + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + + SpawnRule rule = zombieRule(); + rule.minY = spawnY + 4; + CHECK(checkSpawnRule(rule, env, originCell(), MobCap{}) == SpawnDenial::HeightOutOfRange); + + rule.minY = 1; + rule.maxY = spawnY - 1; + CHECK(checkSpawnRule(rule, env, originCell(), MobCap{}) == SpawnDenial::HeightOutOfRange); +} + +VF_TEST(aBiomeOutsideTheRulesListIsRefused) { + World world = plateWorld(Block::Grass); + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + + std::vector rules = defaultSpawnRules(); + SpawnRule creeper = *findSpawnRule(rules, Species::Creeper); + CHECK(!creeper.biomes.empty()); + + env.fallbackBiome = BiomeKind::Ocean; + CHECK(checkSpawnRule(creeper, env, originCell(), MobCap{}) == SpawnDenial::WrongBiome); + + env.fallbackBiome = BiomeKind::Forest; + CHECK(checkSpawnRule(creeper, env, originCell(), MobCap{}) == SpawnDenial::None); +} + +VF_TEST(aNocturnalRuleIsRefusedInBroadDaylight) { + World world = plateWorld(Block::Grass); + LightField light = bakeLight(world); + SpawnEnvironment env = nightEnvironment(world, light); + env.daytime = true; + CHECK(checkSpawnRule(zombieRule(), env, originCell(), MobCap{}) == + SpawnDenial::WrongTimeOfDay); +} +