feat: add spawnHash and hasSpawnRoom to Spawning

This commit is contained in:
dualfroz committed 2026-09-09 18:56:38 +02:00
1 parent ebcace7837
commit be90af709b
1 file changed
+154
+154
View File
@@ -158,3 +158,157 @@ const SpawnRule* findSpawnRule(const std::vector<SpawnRule>& rules, Species spec
return nullptr;
}
int MobCap::baseCapFor(SpawnCategory category) const {
return baseCap[static_cast<std::size_t>(category)];
}
int MobCap::capFor(SpawnCategory category, int loadedChunks) const {
if (loadedChunks <= 0) return 0;
long long scaled = static_cast<long long>(baseCapFor(category)) * loadedChunks;
return static_cast<int>(scaled / chunksPerCapUnit);
}
int SpawnEnvironment::populationOf(SpawnCategory category) const {
return population[static_cast<std::size_t>(category)];
}
void SpawnEnvironment::addPopulation(SpawnCategory category, int amount) {
population[static_cast<std::size_t>(category)] += amount;
}
BiomeKind SpawnEnvironment::biomeAt(int gx, int gz) const {
return biomes ? biomes->biomeAt(gx, gz) : fallbackBiome;
}
int SpawnEnvironment::blockLightAt(const Vec3i& cell) const {
return static_cast<int>(light->blockAtWorld(cell.x, cell.y, cell.z));
}
int SpawnEnvironment::skyLightAt(const Vec3i& cell) const {
int raw = static_cast<int>(light->sunAtWorld(cell.x, cell.y, cell.z));
if (daytime) return raw;
return std::max(0, raw - nightSkyLightPenalty);
}
uint64_t spawnHash(uint64_t seed, int x, int y, int z, int salt) {
uint64_t mixed = seed;
mixed ^= static_cast<uint64_t>(static_cast<uint32_t>(x)) * 0x9e3779b97f4a7c15ull;
mixed ^= static_cast<uint64_t>(static_cast<uint32_t>(y)) * 0xc2b2ae3d27d4eb4full;
mixed ^= static_cast<uint64_t>(static_cast<uint32_t>(z)) * 0x165667b19e3779f9ull;
mixed ^= static_cast<uint64_t>(static_cast<uint32_t>(salt)) * 0x27d4eb2f165667c5ull;
mixed ^= mixed >> 33;
mixed *= 0xff51afd7ed558ccdull;
mixed ^= mixed >> 33;
mixed *= 0xc4ceb9fe1a85ec53ull;
mixed ^= mixed >> 33;
return mixed;
}
bool hasSpawnRoom(const World& world, const Vec3i& cell, int headroom) {
for (int step = 0; step < headroom; ++step) {
if (world.solidAt(cell.x, cell.y + step, cell.z)) return false;
}
return true;
}
SpawnDenial checkSpawnRule(const SpawnRule& rule, const SpawnEnvironment& env, const Vec3i& cell,
const MobCap& cap) {
if (env.populationOf(rule.category) >= cap.capFor(rule.category, env.loadedChunks))
return SpawnDenial::CategoryCapReached;
if (rule.dimension != env.dimension) return SpawnDenial::WrongDimension;
if (cell.y < rule.minY || cell.y > rule.maxY) return SpawnDenial::HeightOutOfRange;
if (!rule.allowsBiome(env.biomeAt(cell.x, cell.z))) return SpawnDenial::WrongBiome;
if (env.daytime && !rule.dayAllowed) return SpawnDenial::WrongTimeOfDay;
if (!env.daytime && !rule.nightAllowed) return SpawnDenial::WrongTimeOfDay;
Block ground = env.world->blockAt(cell.x, cell.y - 1, cell.z);
if (!isSolid(ground) || !rule.allowsGround(ground)) return SpawnDenial::UnsuitableGround;
if (!hasSpawnRoom(*env.world, cell, rule.requiredHeadroom)) return SpawnDenial::NoHeadroom;
int blockLight = env.blockLightAt(cell);
int skyLight = env.skyLightAt(cell);
if (blockLight > rule.maxBlockLight) return SpawnDenial::BlockLightTooHigh;
if (skyLight > rule.maxSkyLight) return SpawnDenial::SkyLightTooHigh;
if (blockLight < rule.minBlockLight) return SpawnDenial::LightTooLow;
if (skyLight < rule.minSkyLight) return SpawnDenial::LightTooLow;
if (env.playerPresent) {
Vec3 spot = behaviorCellCenter(cell);
float distance = straightDistance(spot, env.playerPosition);
if (distance < rule.minPlayerDistance) return SpawnDenial::TooCloseToPlayer;
if (distance > rule.maxPlayerDistance) return SpawnDenial::TooFarFromPlayer;
}
return SpawnDenial::None;
}
bool pickSpawnCell(const SpawnEnvironment& env, const Vec3i& chunkColumn, uint64_t seed,
int attemptIndex, Vec3i& outCell) {
uint64_t roll = spawnHash(seed, chunkColumn.x, attemptIndex, chunkColumn.z, 0x5a);
int localX = static_cast<int>(roll % static_cast<uint64_t>(chunkSize));
int localZ = static_cast<int>((roll >> 8) % static_cast<uint64_t>(chunkSize));
int gx = chunkColumn.x * chunkSize + localX;
int gz = chunkColumn.z * chunkSize + localZ;
for (int y = env.topY; y >= env.bottomY; --y) {
Vec3i probe{gx, y, gz};
if (!isStandable(*env.world, probe, env.path)) continue;
outCell = probe;
return true;
}
return false;
}
std::vector<Vec3> spreadPack(const SpawnRule& rule, const SpawnEnvironment& env,
const Vec3i& origin, int packSize, uint32_t& rng) {
std::vector<Vec3> members;
members.push_back(behaviorCellCenter(origin));
for (int index = 1; index < packSize; ++index) {
int dx = behaviorRandomRange(rng, -rule.packSpread, rule.packSpread);
int dz = behaviorRandomRange(rng, -rule.packSpread, rule.packSpread);
if (dx == 0 && dz == 0) continue;
bool placed = false;
for (int dy = 0; dy <= 2 && !placed; ++dy) {
for (int sign = 1; sign >= -1 && !placed; sign -= 2) {
Vec3i probe{origin.x + dx, origin.y + dy * sign, origin.z + dz};
if (!groundAndRoomFit(rule, env, probe)) continue;
if (!lightFits(rule, env, probe)) continue;
members.push_back(behaviorCellCenter(probe));
placed = true;
}
}
}
return members;
}
DespawnBand despawnBandFor(const DespawnPolicy& policy, const Vec3& position,
const Vec3& playerPosition) {
float distance = straightDistance(position, playerPosition);
if (distance > policy.instantDistance) return DespawnBand::Immediate;
if (distance > policy.randomDistance) return DespawnBand::Random;
return DespawnBand::Protected;
}
bool despawnCandidateIsProtected(const DespawnPolicy& policy, const DespawnCandidate& candidate) {
if (candidate.persistent) return true;
if (candidate.named && !policy.despawnNamed) return true;
if (candidate.leashed && !policy.despawnLeashed) return true;
return false;
}
bool shouldDespawn(const DespawnPolicy& policy, const DespawnCandidate& candidate,
const Vec3& playerPosition, uint32_t& rng) {
if (despawnCandidateIsProtected(policy, candidate)) return false;
DespawnBand band = despawnBandFor(policy, candidate.position, playerPosition);
if (band == DespawnBand::Immediate) return true;
if (band == DespawnBand::Protected) return false;
uint32_t denominator = static_cast<uint32_t>(std::max(1, policy.randomChanceDenominator));
return behaviorNextRandom(rng) % denominator == 0u;
}
SpawnerTickResult tickSpawnerBlock(SpawnerBlockState& spawner, const SpawnEnvironment& env,
int nearbyOfKind) {
SpawnerTickResult outcome;
outcome.delayAfter = spawner.delay;