feat(vf): add SpawnRule and MobCap
This commit is contained in:
1 parent
dcdca6519a
commit
ad742d3423
1 file changed
+155
@@ -0,0 +1,155 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#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<int>(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<int>(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<Block> groundBlocks;
|
||||
int requiredHeadroom = 2;
|
||||
std::vector<BiomeKind> 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<SpawnRule> defaultSpawnRules();
|
||||
const SpawnRule* findSpawnRule(const std::vector<SpawnRule>& 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<int, spawnCategoryCount> 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<int, spawnCategoryCount> 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;
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user