From be90af709bf258babe48791b535de39e5eaebdb2 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 9 Sep 2026 18:56:38 +0200 Subject: [PATCH] feat: add spawnHash and hasSpawnRoom to Spawning --- core/src/Spawning.cpp | 154 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/core/src/Spawning.cpp b/core/src/Spawning.cpp index 966592a..2e7c7d1 100644 --- a/core/src/Spawning.cpp +++ b/core/src/Spawning.cpp @@ -158,3 +158,157 @@ const SpawnRule* findSpawnRule(const std::vector& rules, Species spec return nullptr; } +int MobCap::baseCapFor(SpawnCategory category) const { + return baseCap[static_cast(category)]; +} + +int MobCap::capFor(SpawnCategory category, int loadedChunks) const { + if (loadedChunks <= 0) return 0; + long long scaled = static_cast(baseCapFor(category)) * loadedChunks; + return static_cast(scaled / chunksPerCapUnit); +} + +int SpawnEnvironment::populationOf(SpawnCategory category) const { + return population[static_cast(category)]; +} + +void SpawnEnvironment::addPopulation(SpawnCategory category, int amount) { + population[static_cast(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(light->blockAtWorld(cell.x, cell.y, cell.z)); +} + +int SpawnEnvironment::skyLightAt(const Vec3i& cell) const { + int raw = static_cast(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(static_cast(x)) * 0x9e3779b97f4a7c15ull; + mixed ^= static_cast(static_cast(y)) * 0xc2b2ae3d27d4eb4full; + mixed ^= static_cast(static_cast(z)) * 0x165667b19e3779f9ull; + mixed ^= static_cast(static_cast(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(roll % static_cast(chunkSize)); + int localZ = static_cast((roll >> 8) % static_cast(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 spreadPack(const SpawnRule& rule, const SpawnEnvironment& env, + const Vec3i& origin, int packSize, uint32_t& rng) { + std::vector 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(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; +