feat: extend Spawning
This commit is contained in:
1 parent
be90af709b
commit
20d15c745d
1 file changed
+184
@@ -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
|
||||||
Reference in new issue
Block a user