#pragma once #include #include #include #include "Behavior.h" #include "BiomeGen.h" #include "Block.h" #include "Dimension.h" #include "Lighting.h" #include "Math.h" #include "Mob.h" #include "Pathfind.h" #include "World.h" namespace vf { // Natural spawning: which mob may appear where, how many of them the loaded // world can hold, how they arrive in packs and when they vanish again. Every // decision is a pure function of the world, the rule table and a seed - the // same inputs always produce the same spawn. enum class SpawnCategory : uint8_t { Hostile = 0, Passive = 1, Water = 2, Ambient = 3, Count, }; constexpr int spawnCategoryCount = static_cast(SpawnCategory::Count); const char* spawnCategoryName(SpawnCategory category); SpawnCategory categoryForSpecies(Species species); // Why a spawn did not happen. A refusal always names its reason instead of // collapsing into a bare false. enum class SpawnDenial : uint8_t { None = 0, CategoryCapReached, WrongDimension, WrongBiome, WrongTimeOfDay, HeightOutOfRange, UnsuitableGround, NoHeadroom, BlockLightTooHigh, SkyLightTooHigh, LightTooLow, TooCloseToPlayer, TooFarFromPlayer, NoStandableCell, NoRuleMatched, Count, }; constexpr int spawnDenialCount = static_cast(SpawnDenial::Count); const char* spawnDenialName(SpawnDenial denial); // One species' conditions. An empty biome or ground list means "anything"; // each light bound is checked on its own channel, so a rule can demand open // sky and still refuse a torch-lit cell. struct SpawnRule { Species species = Species::Zombie; SpawnCategory category = SpawnCategory::Hostile; DimensionId dimension = DimensionId::Overworld; int minBlockLight = 0; int maxBlockLight = 15; int minSkyLight = 0; int maxSkyLight = 15; int minY = 0; int maxY = 127; std::vector groundBlocks; int requiredHeadroom = 2; std::vector biomes; bool dayAllowed = true; bool nightAllowed = true; int packMin = 1; int packMax = 4; int packSpread = 3; int weight = 10; float minPlayerDistance = 8.0f; float maxPlayerDistance = 96.0f; bool allowsBiome(BiomeKind biome) const; bool allowsGround(Block block) const; }; // The stock table covers every species Mob.h defines: three hostile and three // passive. Water and Ambient exist as first-class categories with their own // caps and are filled by whatever rules a caller adds. std::vector defaultSpawnRules(); const SpawnRule* findSpawnRule(const std::vector& rules, Species species); // Population ceiling per category, scaled by how much world is actually // loaded: the same base numbers give a small cap for a handful of chunks and // the full cap for a fully loaded view distance. struct MobCap { std::array baseCap{{70, 10, 5, 15}}; int chunksPerCapUnit = 289; int capFor(SpawnCategory category, int loadedChunks) const; int baseCapFor(SpawnCategory category) const; }; // Everything a spawn check reads besides the rule itself. `light` is the baked // field from Lighting.h, so block light and sky light come from the same // source the renderer uses. struct SpawnEnvironment { const World* world = nullptr; const LightField* light = nullptr; const BiomeGen* biomes = nullptr; DimensionId dimension = DimensionId::Overworld; BiomeKind fallbackBiome = BiomeKind::Plains; bool daytime = true; Vec3 playerPosition; bool playerPresent = true; int loadedChunks = 289; int topY = 96; int bottomY = 1; PathConfig path; std::array population{{0, 0, 0, 0}}; int populationOf(SpawnCategory category) const; void addPopulation(SpawnCategory category, int amount); BiomeKind biomeAt(int gx, int gz) const; int blockLightAt(const Vec3i& cell) const; // Sky light as the spawn rules see it: the baked value during the day, // dimmed by the night penalty after dusk, which is what lets hostiles // appear under an open sky only once it is dark. int skyLightAt(const Vec3i& cell) const; }; // The plan for one spawn attempt: which rule won the weighted draw, which cell // the seed picked and how large the pack would be. Nothing here has been // validated yet - that is what attemptSpawn does with it. struct SpawnTrial { Vec3i cell; Species species = Species::Zombie; SpawnCategory category = SpawnCategory::Hostile; int packSize = 0; bool ruleFound = false; bool cellFound = false; };