From 4f1d4588973f3e8881bc48adbf1e871e6e65cc35 Mon Sep 17 00:00:00 2001 From: Dustin Norberg Date: Thu, 8 Oct 2026 02:20:33 -0400 Subject: [PATCH] Map Star Fissures to modern world minimum Y (#58) --- docs/WRITING_GRAMMAR.md | 2 +- gradle.properties | 2 +- .../rivencraft/book/AgeDimensionService.java | 3 +- .../world/AgeFlatChunkGenerator.java | 2 ++ .../rivencraft/world/StarFissureFeature.java | 31 ++++++++++++------- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/docs/WRITING_GRAMMAR.md b/docs/WRITING_GRAMMAR.md index 5716746..714d9e1 100644 --- a/docs/WRITING_GRAMMAR.md +++ b/docs/WRITING_GRAMMAR.md @@ -20,6 +20,6 @@ Material pages are generated for registered full-collision, non-fluid blocks and Crystal Formations are an authored, material-free feature on exposed solid terrain, including Flat terrain; Cave and Void reject the page. Like Legacy, one in fifteen eligible chunks attempts a formation, with the site fixed by the Age seed and chunk coordinates. The modern formation is a compact mound with budding hearts instead of Legacy's long crystal lines. Budding blocks grow through small, medium, and large buds into clusters using vanilla amethyst timing and sounds. Non-silk cluster harvest yields Crystal Shards; four shards craft one Crystal Block. These blocks and shards use 16×16 textures: Legacy's crystal tile for solids and hue-shifted vanilla amethyst sprites for buds and clusters. A page-free Age can also randomly select Crystal Formations; an authored page always retains its exact choice in the saved plan. -Star Fissures are a material-free feature on every terrain profile, including Cave and Void. The fissure is generated once beside the first resolved spawn, using Legacy's spawn-chunk offset and 10–17 paired, irregular X-span rows running along +Z. Each portal tile is placed at y=0 and its column is opened to the sky, as in Legacy. For modern terrain with a surface below y=0, the portal uses the minimum build height plus one instead; empty Void columns keep y=0. Its non-solid portal links to the Overworld spawn with the Legacy fissure sound; the animated starfield is drawn with Minecraft's current End Portal renderer, which uses the same end-sky/end-portal art as Legacy's custom renderer. Random Ages sample Legacy's default small-feature grammar for fissure selection: the stop/one/repeat branches have rank-derived weights 1:1:3, and the fissure has weight 1 of 9 among its small-feature rules. The remainder of random feature selection still follows Rivencraft's current pipeline, rather than running Legacy's entire Age grammar. +Star Fissures are a material-free feature on every terrain profile, including Cave and Void. The fissure is generated once beside the first resolved spawn, using Legacy's spawn-chunk offset and 10–17 paired, irregular X-span rows running along +Z. Legacy's y=0 portal floor maps to the modern dimension's minimum build height, replacing lower bedrock if present. The rift opens to the sky on ordinary terrain; in Cave terrain it opens into the cavern but stops below the upper bedrock roof. Water in the carved columns is cleared before the portal is placed. Its non-solid portal links to the Overworld spawn with the Legacy fissure sound; the animated starfield is drawn with Minecraft's current End Portal renderer, which uses the same end-sky/end-portal art as Legacy's custom renderer. Random Ages sample Legacy's default small-feature grammar for fissure selection: the stop/one/repeat branches have rank-derived weights 1:1:3, and the fissure has weight 1 of 9 among its small-feature rules. The remainder of random feature selection still follows Rivencraft's current pipeline, rather than running Legacy's entire Age grammar. The Rivencraft symbol-page creative tabs read a generated index of bundled symbol definitions and group Pages by each definition's category, except biome-controller roles, which appear in a separate Biome Generation tab rather than the Biome tab. Adding another valid JSON symbol under `src/main/resources/data/rivencraft/rivencraft/symbols/` includes its page in the matching category tab on the next build; a new bundled category receives its own tab. Registry-derived Block and Fluid Pages have separate tabs and both appear in optional JEI. Link Pages have a separate tab. Blank Pages appear in neither the creative tabs nor JEI's ingredient list. The server catalog still decides whether a page is valid for writing. External server data-pack symbols are not included in this bundled index. diff --git a/gradle.properties b/gradle.properties index 67204b0..371f7f2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -36,7 +36,7 @@ mod_name=Rivencraft # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=LGPL-3.0-or-later # The mod version. Rivencraft uses the x.y.z.b policy documented in README.md. -mod_version=0.4.19.1 +mod_version=0.4.19.2 immersive_portals_version=6.0.7 terrablender_version=4.1.0.8 serene_seasons_version_id=B6nTjh4b diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java b/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java index 1a41e00..9403a52 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java @@ -103,7 +103,8 @@ public final class AgeDimensionService { .anyMatch(choice -> choice.id().equals(AgeFeatureSelection.STAR_FISSURES)) || definition.randomFeatures().stream() .anyMatch(choice -> choice.id().equals(AgeFeatureSelection.STAR_FISSURES))) { - StarFissureFeature.generate(level, definition.seed(), spawn); + StarFissureFeature.generate(level, definition.seed(), spawn, + AgeBiomeSelection.isCave(runtimeProfile(definition))); } definition = AgeRegistrySavedData.get(level).setSpawn(definition.id(), spawn); } diff --git a/src/main/java/net/dnorberg/rivencraft/world/AgeFlatChunkGenerator.java b/src/main/java/net/dnorberg/rivencraft/world/AgeFlatChunkGenerator.java index d2d910c..1a86e90 100644 --- a/src/main/java/net/dnorberg/rivencraft/world/AgeFlatChunkGenerator.java +++ b/src/main/java/net/dnorberg/rivencraft/world/AgeFlatChunkGenerator.java @@ -155,6 +155,8 @@ public final class AgeFlatChunkGenerator extends ChunkGenerator { @Override public void applyCarvers(WorldGenRegion level, long seed, RandomState random, BiomeManager biomes, StructureManager structures, ChunkAccess chunk, GenerationStep.Carving step) { } @Override public void spawnOriginalMobs(WorldGenRegion level) { } + // Keep vanilla FlatLevelSource's generator API values. Actual layer + // placement uses chunk.getMinBuildHeight() above, not this metadata value. @Override public int getMinY() { return 0; } @Override public int getGenDepth() { return 384; } @Override public int getSeaLevel() { return -63; } diff --git a/src/main/java/net/dnorberg/rivencraft/world/StarFissureFeature.java b/src/main/java/net/dnorberg/rivencraft/world/StarFissureFeature.java index 35f2458..388ad8e 100644 --- a/src/main/java/net/dnorberg/rivencraft/world/StarFissureFeature.java +++ b/src/main/java/net/dnorberg/rivencraft/world/StarFissureFeature.java @@ -49,29 +49,38 @@ public final class StarFissureFeature { private static void addRow(List columns, int originX, int originZ, int row, int left, int right) { for (int x = left; x <= right; x++) { + // Footprint is a 2D column map; this placeholder Y is never used + // for placement. generate() resolves the real floor from the level. columns.add(new BlockPos(originX + x, 0, originZ + row * 2)); columns.add(new BlockPos(originX + x, 0, originZ + row * 2 + 1)); } } - public static void generate(ServerLevel level, long ageSeed, BlockPos spawn) { + public static void generate(ServerLevel level, long ageSeed, BlockPos spawn, boolean caveTerrain) { List columns = footprint(ageSeed, spawn); - BlockPos first = columns.getFirst(); - // 1.12 worlds began at y=0. Modern superflat can sit below it, so use the - // modern world floor there. Empty Void columns keep Legacy's y=0 portal. - int surface = level.getHeight(Heightmap.Types.WORLD_SURFACE, first.getX(), first.getZ()); - int minY = level.getMinBuildHeight(); - int floorY = surface > minY && surface <= 0 ? minY + 1 : Math.max(0, minY + 1); + // Legacy's y=0 was its minimum build height. The equivalent in 1.21.1 + // is the dimension's minimum Y, including the bedrock layer itself. + int floorY = level.getMinBuildHeight(); BlockPos.MutableBlockPos mutable = new BlockPos.MutableBlockPos(); for (BlockPos column : columns) { int top = level.getHeight(Heightmap.Types.WORLD_SURFACE, column.getX(), column.getZ()); - level.setBlock(mutable.set(column.getX(), floorY, column.getZ()), - ModBlocks.STAR_FISSURE.get().defaultBlockState(), 2); - // Legacy clears all blocks above each portal tile to the sky. Heightmap - // bounds the same work without iterating already-empty world height. + if (caveTerrain) { + // Cave terrain has a bedrock roof. Open the fissure into the + // cavern, but never punch through its upper boundary. + for (int y = Math.max(spawn.getY(), floorY + 1); y < top; y++) { + if (level.getBlockState(mutable.set(column.getX(), y, column.getZ())).is(Blocks.BEDROCK)) { + top = y; + break; + } + } + } + // Clear the full column first, including water. Place the portal + // last so an existing fluid cannot occupy its block during carving. for (int y = floorY + 1; y < top; y++) { level.setBlock(mutable.set(column.getX(), y, column.getZ()), Blocks.AIR.defaultBlockState(), 2); } + level.setBlock(mutable.set(column.getX(), floorY, column.getZ()), + ModBlocks.STAR_FISSURE.get().defaultBlockState(), 2); } } }