diff --git a/README.md b/README.md index 5815b96..246832f 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,9 @@ Open a page-free Descriptive Book, name its Age, and press Create. Rivencraft assigns the book a permanent Age UUID, creates a dedicated vanilla-like runtime dimension, and prepares a five-by-five cobblestone arrival platform at the highest suitable nearby terrain. Solid blocks and tree leaves are valid -footing. If no suitable surface is available, the -platform is generated at Y=63. Reopen the book and click its preview to travel. +footing. Candidate columns are scanned from the top down so cave floors are not +selected, and fluid-covered columns are excluded. If no suitable surface is +available, the platform is generated at Y=63. Reopen the book and click its preview to travel. Copies of the written book continue to resolve to the same persistent Age across server restarts. diff --git a/docs/PORTING.md b/docs/PORTING.md index 25372b5..2c0e5f6 100644 --- a/docs/PORTING.md +++ b/docs/PORTING.md @@ -202,6 +202,10 @@ no suitable natural footing exists. Version 0.2.10.2 allows leaf blocks to count as proper natural footing when selecting the highest arrival surface. +Version 0.2.10.3 scans each candidate column downward from the dimension's top +before choosing footing, preventing cave floors from being selected as the Age +arrival surface. Columns covered by fluid remain ineligible. + The preview code is separated behind a renderer interface and uses an isolated Ponder level populated from server-authoritative destination snapshots. diff --git a/gradle.properties b/gradle.properties index d34e415..aa70f67 100644 --- a/gradle.properties +++ b/gradle.properties @@ -35,7 +35,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.2.10.2 +mod_version=0.2.10.3 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java b/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java index 8a67f76..ac7c80b 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java @@ -121,18 +121,12 @@ public final class AgeDimensionService { for (int x = -SURFACE_SEARCH_RADIUS; x <= SURFACE_SEARCH_RADIUS; x++) { for (int z = -SURFACE_SEARCH_RADIUS; z <= SURFACE_SEARCH_RADIUS; z++) { - int floorY = level.getHeight(Heightmap.Types.WORLD_SURFACE, x, z) - 1; - if (floorY < level.getMinBuildHeight() || floorY > maximumFloorY) { + Optional columnFooting = highestFootingInColumn(level, x, z, maximumFloorY); + if (columnFooting.isEmpty()) { continue; } - BlockPos candidate = new BlockPos(x, floorY, z); - var state = level.getBlockState(candidate); - if (!level.getFluidState(candidate).isEmpty() - || (!state.is(BlockTags.LEAVES) - && !state.isFaceSturdy(level, candidate, net.minecraft.core.Direction.UP))) { - continue; - } + BlockPos candidate = columnFooting.get(); if (best == null || candidate.getY() > best.getY() || (candidate.getY() == best.getY() @@ -145,6 +139,27 @@ public final class AgeDimensionService { return Optional.ofNullable(best); } + /** Scans downward from the top so cave floors cannot be mistaken for surface footing. */ + private static Optional highestFootingInColumn(ServerLevel level, int x, int z, int maximumFloorY) { + BlockPos.MutableBlockPos candidate = new BlockPos.MutableBlockPos(x, maximumFloorY, z); + for (int y = maximumFloorY; y >= level.getMinBuildHeight(); y--) { + candidate.setY(y); + if (!level.getFluidState(candidate).isEmpty()) { + return Optional.empty(); + } + + var state = level.getBlockState(candidate); + if (state.isAir()) { + continue; + } + if (state.is(BlockTags.LEAVES) + || state.isFaceSturdy(level, candidate, net.minecraft.core.Direction.UP)) { + return Optional.of(candidate.immutable()); + } + } + return Optional.empty(); + } + private static BlockPos fallbackSpawnFloor(ServerLevel level) { int floorY = Math.clamp(FALLBACK_PLATFORM_Y, level.getMinBuildHeight(), level.getMaxBuildHeight() - 5);