diff --git a/Minecraft.World/QuicksoilShelfFeature.cpp b/Minecraft.World/QuicksoilShelfFeature.cpp index f6d9513c..729dc3e3 100644 --- a/Minecraft.World/QuicksoilShelfFeature.cpp +++ b/Minecraft.World/QuicksoilShelfFeature.cpp @@ -7,56 +7,146 @@ QuicksoilShelfFeature::QuicksoilShelfFeature() : Feature(false) { } +static bool isIslandBlock(int tile) +{ + return tile == Tile::holystone_Id || tile == Tile::aetherDirt_Id; +} + bool QuicksoilShelfFeature::place(Level *level, Random *random, int x, int y, int z) { - // Find the bottom of the island at this XZ column - // Start from the given Y and scan downward to find the lowest solid block - int bottomY = -1; + // scan down to find holystone/aether dirt + int placeY = -1; for (int yy = y; yy >= 1; yy--) { int tile = level->getTile(x, yy, z); - if (tile != 0) + if (isIslandBlock(tile)) { - // Check if the block below is air — this is the island bottom - if (level->getTile(x, yy - 1, z) == 0) + placeY = yy; + break; + } + } + + if (placeY < 1) return false; + + // make sure this block is actually exposed and not buried inside the island + bool exposedToAir = false; + int neighborOffsets[][3] = { {1,0,0}, {-1,0,0}, {0,0,1}, {0,0,-1}, {0,-1,0}, {0,1,0} }; + for (int n = 0; n < 6; n++) + { + if (level->getTile(x + neighborOffsets[n][0], placeY + neighborOffsets[n][1], z + neighborOffsets[n][2]) == 0) + { + exposedToAir = true; + break; + } + } + if (!exposedToAir) return false; + + // need more island blocks above so we know this isn't just a tree or random block + bool hasIslandNeighborAbove = false; + for (int cy = placeY + 1; cy <= placeY + 3; cy++) + { + if (isIslandBlock(level->getTile(x, cy, z)) || level->getTile(x, cy, z) == Tile::aetherGrass_Id) + { + hasIslandNeighborAbove = true; + break; + } + } + if (!hasIslandNeighborAbove) return false; + + // check that we're actually near the edge of the island by looking + // for open air in at least one cardinal direction + const int scanDist = 8; + const int airThreshold = 5; + bool nearEdge = false; + + int dirX[] = { 0, 0, 1, -1 }; + int dirZ[] = { 1, -1, 0, 0 }; + + for (int d = 0; d < 4; d++) + { + int airCount = 0; + for (int i = 1; i <= scanDist; i++) + { + int sx = x + dirX[d] * i; + int sz = z + dirZ[d] * i; + + // if this whole column is air, count it + bool columnIsAir = true; + for (int cy = placeY - 2; cy <= placeY + 2; cy++) { - bottomY = yy; - break; + if (level->getTile(sx, cy, sz) != 0) + { + columnIsAir = false; + break; + } + } + + if (columnIsAir) + airCount++; + } + + if (airCount >= airThreshold) + { + nearEdge = true; + break; + } + } + + if (!nearEdge) return false; + + // bail out if there's already quicksoil nearby so we don't get clusters + const int spacingRadius = 8; + for (int sx = -spacingRadius; sx <= spacingRadius; sx++) + { + for (int sz = -spacingRadius; sz <= spacingRadius; sz++) + { + for (int sy = -3; sy <= 3; sy++) + { + if (level->getTile(x + sx, placeY + sy, z + sz) == Tile::quicksoil_Id) + return false; } } } - if (bottomY < 1) return false; + // place the shelf - flat quicksoil balcony at one Y level + // grows outward each pass by attaching to existing blocks + int shelfY = placeY; + int radius = 3 + random->nextInt(4); + int outwardPasses = 3 + random->nextInt(3); - // Generate a shelf of quicksoil on the underside of the island - // Place quicksoil in a roughly circular blob hanging from the bottom - int radius = 2 + random->nextInt(3); // radius 2-4 - int depth = 1 + random->nextInt(2); // depth 1-2 - - for (int dx = -radius; dx <= radius; dx++) + for (int pass = 0; pass < outwardPasses; pass++) { - for (int dz = -radius; dz <= radius; dz++) + for (int dx = -radius; dx <= radius; dx++) { - // Circular shape with some randomness - float dist = sqrt((float)(dx * dx + dz * dz)); - if (dist > radius + 0.5f) continue; - - // More likely to place at center, less at edges - if (dist > radius - 1 && random->nextInt(3) != 0) continue; - - int px = x + dx; - int pz = z + dz; - - for (int dy = 0; dy < depth; dy++) + for (int dz = -radius; dz <= radius; dz++) { - int py = bottomY - dy; - if (py < 1) continue; + float dist = sqrt((float)(dx * dx + dz * dz)); + if (dist > radius + 0.5f) continue; - int existing = level->getTile(px, py, pz); - // Only replace air blocks or existing holystone/aetherDirt at the underside - if (existing == 0 || existing == Tile::holystone_Id || existing == Tile::aetherDirt_Id) + // thin out towards the edges + if (dist > radius - 1 && random->nextInt(3) != 0) continue; + + int px = x + dx; + int pz = z + dz; + + if (level->getTile(px, shelfY, pz) != 0) continue; + + // only attach to adjacent solid blocks (sides + below, not above) + bool canAttach = false; + int hOffsets[][3] = { {1,0,0}, {-1,0,0}, {0,0,1}, {0,0,-1}, {0,-1,0} }; + for (int n = 0; n < 5; n++) { - level->setTileNoUpdate(px, py, pz, Tile::quicksoil_Id); + int neighbor = level->getTile(px + hOffsets[n][0], shelfY + hOffsets[n][1], pz + hOffsets[n][2]); + if (isIslandBlock(neighbor) || neighbor == Tile::quicksoil_Id) + { + canAttach = true; + break; + } + } + + if (canAttach) + { + level->setTileNoUpdate(px, shelfY, pz, Tile::quicksoil_Id); } } }