feat: add spawnHash and hasSpawnRoom to Spawning
This commit is contained in:
1 parent
ebcace7837
commit
be90af709b
1 file changed
+154
@@ -158,3 +158,157 @@ const SpawnRule* findSpawnRule(const std::vector<SpawnRule>& rules, Species spec
|
|||||||
return nullptr;
|
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;
|
||||||
|
|
||||||
Reference in new issue
Block a user