feat(vf): add SpawnResult and DespawnPolicy to Spawning

This commit is contained in:
dualfroz committed 2026-09-04 21:43:49 +02:00
1 parent ad742d3423
commit 66cd109fe1
1 file changed
+141
+141
View File
@@ -153,3 +153,144 @@ struct SpawnTrial {
bool cellFound = false;
};
struct SpawnResult {
bool spawned = false;
SpawnDenial denial = SpawnDenial::NoRuleMatched;
Species species = Species::Zombie;
SpawnCategory category = SpawnCategory::Hostile;
BehaviorRole role = BehaviorRole::HostileHunter;
Vec3i origin;
std::vector<Vec3> positions;
int packSize() const { return static_cast<int>(positions.size()); }
};
// Deterministic 64-bit mix. Same seed, same cell, same salt, same number.
uint64_t spawnHash(uint64_t seed, int x, int y, int z, int salt);
// Standable, has the headroom the rule wants and nothing solid in the way.
bool hasSpawnRoom(const World& world, const Vec3i& cell, int headroom);
// The full per-cell verdict, checked in a fixed order so a caller can tell
// exactly which condition failed.
SpawnDenial checkSpawnRule(const SpawnRule& rule, const SpawnEnvironment& env, const Vec3i& cell,
const MobCap& cap);
// Picks the highest standable cell in one column of the chunk, chosen from the
// seed rather than scanned in order.
bool pickSpawnCell(const SpawnEnvironment& env, const Vec3i& chunkColumn, uint64_t seed,
int attemptIndex, Vec3i& outCell);
// Spreads a pack around an accepted origin. Members that land somewhere the
// rule rejects are simply left out; the origin is always member zero.
std::vector<Vec3> spreadPack(const SpawnRule& rule, const SpawnEnvironment& env,
const Vec3i& origin, int packSize, uint32_t& rng);
enum class DespawnBand : uint8_t {
Protected = 0,
Random = 1,
Immediate = 2,
};
// Distance bands around the player. Beyond `instantDistance` a mob is gone the
// moment it is checked; between the two distances it rolls every tick; closer
// than `randomDistance` it never despawns on its own.
struct DespawnPolicy {
float instantDistance = 128.0f;
float randomDistance = 32.0f;
int randomChanceDenominator = 800;
bool despawnNamed = false;
bool despawnLeashed = false;
};
struct DespawnCandidate {
Vec3 position;
bool named = false;
bool leashed = false;
bool persistent = false;
};
DespawnBand despawnBandFor(const DespawnPolicy& policy, const Vec3& position,
const Vec3& playerPosition);
bool despawnCandidateIsProtected(const DespawnPolicy& policy, const DespawnCandidate& candidate);
bool shouldDespawn(const DespawnPolicy& policy, const DespawnCandidate& candidate,
const Vec3& playerPosition, uint32_t& rng);
// A monster spawner block: counts down, refuses to fire without a player in
// range or with too many of its own kind already nearby, then picks a fresh
// delay from its range.
struct SpawnerBlockState {
Species species = Species::Zombie;
Vec3i position;
int spawnCount = 4;
int spawnRange = 4;
int nearbyLimit = 6;
int requiredHeadroom = 2;
float activationRange = 16.0f;
int minDelay = 200;
int maxDelay = 800;
int delay = 20;
int totalSpawned = 0;
int cyclesFired = 0;
uint32_t rng = 0x1f123bb5u;
};
struct SpawnerTickResult {
bool playerInRange = false;
bool fired = false;
SpawnDenial denial = SpawnDenial::None;
std::vector<Vec3> positions;
int delayAfter = 0;
};
SpawnerTickResult tickSpawnerBlock(SpawnerBlockState& spawner, const SpawnEnvironment& env,
int nearbyOfKind);
// Runs spawn cycles over loaded chunk columns and keeps a per-reason tally of
// everything it refused, so a world that never spawns anything can be
// diagnosed instead of guessed at.
class Spawner {
public:
Spawner();
explicit Spawner(std::vector<SpawnRule> ruleTable, const MobCap& caps = {});
const std::vector<SpawnRule>& rules() const { return ruleTable; }
const MobCap& caps() const { return capTable; }
void setAttemptsPerColumn(int attempts) { attemptsPerColumnCount = attempts; }
int attemptsPerColumn() const { return attemptsPerColumnCount; }
// Weighted pick among the rules of one category that fit the dimension.
const SpawnRule* pickRule(const SpawnEnvironment& env, SpawnCategory category,
uint64_t seed) const;
// Draws a rule and a cell without touching the world state.
SpawnTrial planSpawn(const SpawnEnvironment& env, SpawnCategory category,
const Vec3i& chunkColumn, uint64_t seed, int attemptIndex) const;
SpawnResult attemptSpawn(const SpawnEnvironment& env, const SpawnRule& rule,
const SpawnTrial& trial, uint64_t seed);
SpawnResult runColumn(SpawnEnvironment& env, SpawnCategory category, const Vec3i& chunkColumn,
uint64_t seed, int attemptIndex);
std::vector<SpawnResult> runCycle(SpawnEnvironment& env, SpawnCategory category,
const std::vector<Vec3i>& chunkColumns, uint64_t seed);
int denialCount(SpawnDenial denial) const;
int spawnedCount() const { return spawned; }
void resetStats();
private:
std::vector<SpawnRule> ruleTable;
MobCap capTable;
int attemptsPerColumnCount = 3;
int spawned = 0;
std::array<int, spawnDenialCount> denials{};
};
// Hands a finished spawn straight to the behaviour layer: every member of the
// pack becomes an agent already carrying the tree role for its species.
std::vector<BehaviorAgent> agentsFromSpawn(const SpawnResult& result, uint32_t seed);
} // namespace vf