From 8884fe5a537b890687ee37599baf1b6233f85080 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 7 Sep 2026 19:00:16 +0200 Subject: [PATCH] feat: extend EndGen --- core/src/EndGen.cpp | 184 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/core/src/EndGen.cpp b/core/src/EndGen.cpp index 5a12017..4a571b7 100644 --- a/core/src/EndGen.cpp +++ b/core/src/EndGen.cpp @@ -158,3 +158,187 @@ std::vector EndGenerator::pillars() const { std::vector ring; ring.reserve(static_cast(params.pillarCount)); + for (int index = 0; index < params.pillarCount; ++index) { + float angle = 6.2831853f * static_cast(index) / + static_cast(params.pillarCount); + uint32_t roll = mixCoord(params.seed ^ 0x50494c4cu, index, 0, params.pillarRing); + + int spread = params.pillarRing + static_cast(roll % 9u) - 4; + int gx = static_cast(std::lround(std::cos(angle) * static_cast(spread))); + int gz = static_cast(std::lround(std::sin(angle) * static_cast(spread))); + + ObsidianPillar pillar; + pillar.base = {gx, surfaceHeight(gx, gz), gz}; + pillar.radius = 1 + static_cast((roll >> 8) % 3u); + pillar.height = 22 + static_cast((roll >> 12) % 19u); + pillar.crowned = pillar.height >= 34; + + int top = pillar.base.y + pillar.height; + if (top >= params.ceilingY - 2) pillar.height = params.ceilingY - 3 - pillar.base.y; + ring.push_back(pillar); + } + return ring; +} + +void EndGenerator::generateChunk(Chunk& chunk, const Vec3i& chunkCoord) const { + int ox = chunkCoord.x * chunkSize; + int oy = chunkCoord.y * chunkSize; + int oz = chunkCoord.z * chunkSize; + + for (int lx = 0; lx < chunkSize; ++lx) { + for (int lz = 0; lz < chunkSize; ++lz) { + int topY = 0, bottomY = 0; + bool solidColumn = columnExtent(ox + lx, oz + lz, topY, bottomY); + for (int ly = 0; ly < chunkSize; ++ly) { + int gy = oy + ly; + bool stone = solidColumn && gy >= bottomY && gy <= topY; + chunk.set(lx, ly, lz, stone ? Block::EndStone : Block::Air); + } + } + } +} + +void EndGenerator::stampPillars(World& world, int minX, int minZ, int maxX, int maxZ) const { + for (const ObsidianPillar& pillar : pillars()) { + if (pillar.height <= 0) continue; + int reach = pillar.radius; + for (int dx = -reach; dx <= reach; ++dx) { + for (int dz = -reach; dz <= reach; ++dz) { + if (dx * dx + dz * dz > reach * reach) continue; + int gx = pillar.base.x + dx; + int gz = pillar.base.z + dz; + if (gx < minX || gx > maxX || gz < minZ || gz > maxZ) continue; + + int bottom = pillar.base.y - 3; + if (bottom < params.floorY) bottom = params.floorY; + for (int y = bottom; y <= pillar.base.y + pillar.height; ++y) + world.setBlock(gx, y, gz, Block::Obsidian); + } + } + if (!pillar.crowned) continue; + int crownX = pillar.base.x; + int crownZ = pillar.base.z; + if (crownX < minX || crownX > maxX || crownZ < minZ || crownZ > maxZ) continue; + world.setBlock(crownX, pillar.base.y + pillar.height + 1, crownZ, Block::CryingObsidian); + } +} + +void EndGenerator::placePillars(World& world) const { + stampPillars(world, -unboundedSpan, -unboundedSpan, unboundedSpan, unboundedSpan); +} + +void EndGenerator::stampExitPortal(World& world, int minX, int minZ, int maxX, int maxZ) const { + int surface = surfaceHeight(0, 0); + if (surface < params.floorY) return; + + for (int dx = -2; dx <= 2; ++dx) { + for (int dz = -2; dz <= 2; ++dz) { + if (dx < minX || dx > maxX || dz < minZ || dz > maxZ) continue; + + world.setBlock(dx, surface - 1, dz, Block::Bedrock); + bool inner = std::abs(dx) <= 1 && std::abs(dz) <= 1; + world.setBlock(dx, surface, dz, inner ? Block::EndPortal : Block::Bedrock); + for (int up = 1; up <= 3; ++up) world.setBlock(dx, surface + up, dz, Block::Air); + } + } + + const int cornerX[4] = {-2, -2, 2, 2}; + const int cornerZ[4] = {-2, 2, -2, 2}; + for (int corner = 0; corner < 4; ++corner) { + int gx = cornerX[corner]; + int gz = cornerZ[corner]; + if (gx < minX || gx > maxX || gz < minZ || gz > maxZ) continue; + for (int up = 1; up <= 3; ++up) world.setBlock(gx, surface + up, gz, Block::Bedrock); + } +} + +void EndGenerator::placeExitPortal(World& world) const { + stampExitPortal(world, -unboundedSpan, -unboundedSpan, unboundedSpan, unboundedSpan); +} + +int EndGenerator::growBranch(World& world, Vec3i pos, int depth, int maxDepth, + uint32_t& rngState) const { + if (pos.y >= params.ceilingY - 2 || depth >= maxDepth) { + if (world.blockAt(pos.x, pos.y, pos.z) != Block::Air) return 0; + world.setBlock(pos.x, pos.y, pos.z, Block::ChorusFlower); + return 1; + } + + int written = 0; + int segments = 1 + static_cast(nextState(rngState) % 3u); + for (int step = 0; step < segments && pos.y < params.ceilingY - 2; ++step) { + if (world.blockAt(pos.x, pos.y, pos.z) != Block::Air) break; + world.setBlock(pos.x, pos.y, pos.z, Block::ChorusPlant); + ++written; + pos.y += 1; + } + + int branches = static_cast(nextState(rngState) % 3u); + for (int branch = 0; branch < branches; ++branch) { + const Vec3i& step = horizontalSteps[nextState(rngState) % 4u]; + Vec3i side{pos.x + step.x, pos.y - 1, pos.z + step.z}; + if (world.blockAt(side.x, side.y, side.z) != Block::Air) continue; + world.setBlock(side.x, side.y, side.z, Block::ChorusPlant); + ++written; + written += growBranch(world, {side.x, side.y + 1, side.z}, depth + 1, maxDepth, rngState); + } + + written += growBranch(world, pos, depth + 1, maxDepth, rngState); + return written; +} + +int EndGenerator::growChorus(World& world, const Vec3i& base, uint32_t roll, int maxDepth) const { + if (world.blockAt(base.x, base.y - 1, base.z) != Block::EndStone) return 0; + if (world.blockAt(base.x, base.y, base.z) != Block::Air) return 0; + + uint32_t state = mixCoord(params.seed ^ 0x43484f52u, base.x, base.y, base.z) ^ roll; + return growBranch(world, base, 0, maxDepth, state); +} + +void EndGenerator::raisePurpurSpire(World& world, int gx, int gz, int top, uint32_t roll) const { + int tall = 2 + static_cast((roll >> 18) % 4u); + for (int step = 0; step < tall; ++step) { + int y = top + 1 + step; + if (y >= params.ceilingY - 1) return; + if (world.blockAt(gx, y, gz) != Block::Air) return; + world.setBlock(gx, y, gz, Block::Purpur); + } +} + +void EndGenerator::decorateColumn(World& world, int columnX, int columnZ) const { + int ox = columnX * chunkSize; + int oz = columnZ * chunkSize; + + for (int lx = 4; lx < chunkSize - 4; ++lx) { + for (int lz = 4; lz < chunkSize - 4; ++lz) { + int gx = ox + lx; + int gz = oz + lz; + if (biomeAt(gx, gz) != EndBiome::OuterIsland) continue; + + int top = surfaceHeight(gx, gz); + if (top < params.floorY) continue; + uint32_t roll = mixCoord(params.seed ^ 0x44454330u, gx, 0, gz); + + if (params.chorus && roll % 19u == 0u) growChorus(world, {gx, top + 1, gz}, roll, 3); + else if (roll % 53u == 7u) raisePurpurSpire(world, gx, gz, top, roll); + } + } +} + +void EndGenerator::generateColumn(World& world, int columnX, int columnZ) const { + for (int cy = 0; cy < stackChunkCount(); ++cy) { + Chunk& chunk = world.ensureChunk({columnX, cy, columnZ}); + generateChunk(chunk, {columnX, cy, columnZ}); + } + + int minX = columnX * chunkSize; + int minZ = columnZ * chunkSize; + int maxX = minX + chunkSize - 1; + int maxZ = minZ + chunkSize - 1; + + stampPillars(world, minX, minZ, maxX, maxZ); + if (params.exitPortal) stampExitPortal(world, minX, minZ, maxX, maxZ); + decorateColumn(world, columnX, columnZ); +} + +} // namespace vf