feat: extend Spawning

This commit is contained in:
dualfroz committed 2026-09-09 18:58:04 +02:00
1 parent be90af709b
commit 20d15c745d
1 file changed
+184
+184
View File
@@ -312,3 +312,187 @@ SpawnerTickResult tickSpawnerBlock(SpawnerBlockState& spawner, const SpawnEnviro
SpawnerTickResult outcome; SpawnerTickResult outcome;
outcome.delayAfter = spawner.delay; outcome.delayAfter = spawner.delay;
Vec3 blockCenter = behaviorCellCenter(spawner.position);
outcome.playerInRange =
env.playerPresent && straightDistance(blockCenter, env.playerPosition) <=
spawner.activationRange;
if (!outcome.playerInRange) {
outcome.denial = SpawnDenial::TooFarFromPlayer;
return outcome;
}
if (spawner.delay > 0) {
--spawner.delay;
outcome.delayAfter = spawner.delay;
return outcome;
}
if (nearbyOfKind >= spawner.nearbyLimit) {
outcome.denial = SpawnDenial::CategoryCapReached;
spawner.delay = behaviorRandomRange(spawner.rng, spawner.minDelay, spawner.maxDelay);
outcome.delayAfter = spawner.delay;
return outcome;
}
for (int index = 0; index < spawner.spawnCount; ++index) {
int dx = behaviorRandomRange(spawner.rng, -spawner.spawnRange, spawner.spawnRange);
int dy = behaviorRandomRange(spawner.rng, -1, 1);
int dz = behaviorRandomRange(spawner.rng, -spawner.spawnRange, spawner.spawnRange);
Vec3i probe{spawner.position.x + dx, spawner.position.y + dy, spawner.position.z + dz};
if (!env.world->solidAt(probe.x, probe.y - 1, probe.z)) continue;
if (!hasSpawnRoom(*env.world, probe, spawner.requiredHeadroom)) continue;
outcome.positions.push_back(behaviorCellCenter(probe));
}
if (outcome.positions.empty()) {
outcome.denial = SpawnDenial::NoHeadroom;
} else {
outcome.fired = true;
spawner.totalSpawned += static_cast<int>(outcome.positions.size());
++spawner.cyclesFired;
}
spawner.delay = behaviorRandomRange(spawner.rng, spawner.minDelay, spawner.maxDelay);
outcome.delayAfter = spawner.delay;
return outcome;
}
Spawner::Spawner() : ruleTable(defaultSpawnRules()) {}
Spawner::Spawner(std::vector<SpawnRule> rules, const MobCap& caps)
: ruleTable(std::move(rules)), capTable(caps) {}
const SpawnRule* Spawner::pickRule(const SpawnEnvironment& env, SpawnCategory category,
uint64_t seed) const {
int totalWeight = 0;
for (const SpawnRule& rule : ruleTable) {
if (rule.category != category) continue;
if (rule.dimension != env.dimension) continue;
if (rule.weight <= 0) continue;
totalWeight += rule.weight;
}
if (totalWeight == 0) return nullptr;
int pick = static_cast<int>(seed % static_cast<uint64_t>(totalWeight));
for (const SpawnRule& rule : ruleTable) {
if (rule.category != category) continue;
if (rule.dimension != env.dimension) continue;
if (rule.weight <= 0) continue;
pick -= rule.weight;
if (pick < 0) return &rule;
}
return nullptr;
}
SpawnTrial Spawner::planSpawn(const SpawnEnvironment& env, SpawnCategory category,
const Vec3i& chunkColumn, uint64_t seed, int attemptIndex) const {
SpawnTrial trial;
trial.category = category;
uint64_t ruleSeed = spawnHash(seed, chunkColumn.x, attemptIndex, chunkColumn.z, 0x11);
const SpawnRule* rule = pickRule(env, category, ruleSeed);
if (rule == nullptr) return trial;
trial.ruleFound = true;
trial.species = rule->species;
trial.category = rule->category;
if (!pickSpawnCell(env, chunkColumn, seed, attemptIndex, trial.cell)) return trial;
trial.cellFound = true;
uint32_t packRng =
static_cast<uint32_t>(spawnHash(seed, trial.cell.x, trial.cell.y, trial.cell.z, 0x9c));
trial.packSize = behaviorRandomRange(packRng, rule->packMin, rule->packMax);
return trial;
}
SpawnResult Spawner::attemptSpawn(const SpawnEnvironment& env, const SpawnRule& rule,
const SpawnTrial& trial, uint64_t seed) {
SpawnResult result;
result.species = rule.species;
result.category = rule.category;
result.role = roleForSpecies(rule.species);
result.origin = trial.cell;
result.denial = checkSpawnRule(rule, env, trial.cell, capTable);
if (result.denial != SpawnDenial::None) {
++denials[static_cast<std::size_t>(result.denial)];
return result;
}
uint32_t rng = static_cast<uint32_t>(
spawnHash(seed, trial.cell.x, trial.cell.y, trial.cell.z, 0x9c));
result.positions = spreadPack(rule, env, trial.cell, trial.packSize, rng);
result.spawned = true;
++spawned;
return result;
}
SpawnResult Spawner::runColumn(SpawnEnvironment& env, SpawnCategory category,
const Vec3i& chunkColumn, uint64_t seed, int attemptIndex) {
SpawnTrial trial = planSpawn(env, category, chunkColumn, seed, attemptIndex);
SpawnResult result;
result.category = category;
result.species = trial.species;
if (!trial.ruleFound) {
result.denial = SpawnDenial::NoRuleMatched;
++denials[static_cast<std::size_t>(result.denial)];
return result;
}
if (!trial.cellFound) {
result.denial = SpawnDenial::NoStandableCell;
++denials[static_cast<std::size_t>(result.denial)];
return result;
}
uint64_t ruleSeed = spawnHash(seed, chunkColumn.x, attemptIndex, chunkColumn.z, 0x11);
result = attemptSpawn(env, *findSpawnRule(ruleTable, trial.species), trial, ruleSeed);
if (result.spawned) env.addPopulation(category, result.packSize());
return result;
}
std::vector<SpawnResult> Spawner::runCycle(SpawnEnvironment& env, SpawnCategory category,
const std::vector<Vec3i>& chunkColumns, uint64_t seed) {
std::vector<SpawnResult> results;
results.reserve(chunkColumns.size() * static_cast<std::size_t>(attemptsPerColumnCount));
for (const Vec3i& column : chunkColumns) {
for (int attempt = 0; attempt < attemptsPerColumnCount; ++attempt) {
results.push_back(runColumn(env, category, column, seed, attempt));
}
}
return results;
}
int Spawner::denialCount(SpawnDenial denial) const {
return denials[static_cast<std::size_t>(denial)];
}
void Spawner::resetStats() {
denials.fill(0);
spawned = 0;
}
std::vector<BehaviorAgent> agentsFromSpawn(const SpawnResult& result, uint32_t seed) {
std::vector<BehaviorAgent> agents;
if (!result.spawned) return agents;
const SpeciesStats& stats = speciesStats(result.species);
agents.reserve(result.positions.size());
uint32_t stream = seed != 0u ? seed : 0x9e3779b9u;
for (const Vec3& spot : result.positions) {
BehaviorAgent agent;
agent.species = result.species;
agent.position = spot;
agent.homePosition = spot;
agent.health = stats.maxHealth;
agent.maxHealth = stats.maxHealth;
agent.moveStep = stats.walkSpeed / static_cast<float>(ticksPerSecond);
agent.rng = behaviorNextRandom(stream);
agents.push_back(agent);
}
return agents;
}
} // namespace vf