From 9ccec18c18b4ecba1e1cc4b59fe5bf091a9793f9 Mon Sep 17 00:00:00 2001 From: Dustin Norberg Date: Sun, 1 Nov 2026 01:34:14 -0500 Subject: [PATCH] Port dry Cave Ages and safe landing to 26.3 beta --- docs/MINECRAFT_26_3_PORT.md | 13 +++++++ ports/mc263/README.md | 19 +++++++++- ports/mc263/RELEASE_NOTES_1.0.1.39-beta.1.md | 14 +++++++ ports/mc263/gradle.properties | 2 +- .../rivencraft/age/PortAgeCommands.java | 19 ++++++---- .../rivencraft/age/PortAgeTerrain.java | 1 + .../rivencraft/age/PortAgeTravel.java | 38 ++++++++++++++++++- .../rivencraft/age/RuntimeAgeBackend.java | 26 ++++++++++++- .../writing/PortAuthoredWriting.java | 12 ++++-- .../rivencraft/writing/PortRegistryPages.java | 4 +- .../tags/worldgen/biome/cave_biomes.json | 8 ++++ scripts/smoke_server_263.sh | 15 +++++++- 12 files changed, 153 insertions(+), 18 deletions(-) create mode 100644 ports/mc263/RELEASE_NOTES_1.0.1.39-beta.1.md create mode 100644 ports/mc263/src/main/resources/data/rivencraft/tags/worldgen/biome/cave_biomes.json diff --git a/docs/MINECRAFT_26_3_PORT.md b/docs/MINECRAFT_26_3_PORT.md index 5815d87..b3f5261 100644 --- a/docs/MINECRAFT_26_3_PORT.md +++ b/docs/MINECRAFT_26_3_PORT.md @@ -495,6 +495,19 @@ accepts the existing authored distribution pages for those terrains. New Flat chunks can vary vegetation by biome; Void keeps biome climate without adding a floor or decorations. These are beta-port slices, not full 1.21.1 parity. +The local `1.0.1.37-beta.1` slice ports Cave terrain authoring using the +26.3 caves noise preset. Cave and Nether Biome pages can be distributed +through an Age, and travel searches for footing from Y=64 downward rather +than mistaking the cave roof for the surface. Nether-style cave material +replacement from 1.21.1 remains a separate port task. The local +`1.0.1.38-beta.1` follow-up +keeps Cave Ages dry by default: the vanilla 26.3 Cave preset otherwise fills +low openings with water. This changes newly generated chunks only; a future +Flooded page will deliberately set an authored water level in both maintained +versions. +The local `1.0.1.39-beta.1` follow-up makes the 26.3 landing search reject +fluid at feet and head height and prevents a submerged fallback platform. + ## Verification of this first slice The 1.21.1 `compileJava` and `build` tasks passed offline after the backend diff --git a/ports/mc263/README.md b/ports/mc263/README.md index ccb0c32..37755c6 100644 --- a/ports/mc263/README.md +++ b/ports/mc263/README.md @@ -3,7 +3,7 @@ This is an **experimental beta port** for issues #97–#104, not gameplay parity with the Minecraft 1.21.1 release. The 26.3 backend persists separate Ages and their seeds. Books, the Book Binder, and the Ink Mixer work, and the Binder can -author Standard, Amplified, Island, Flat, or Void terrain with supported +author Standard, Amplified, Island, Flat, Void, or Cave terrain with supported biome-distribution pages. Other terrain families, feature/material effects, portals, and much of the 1.21.1 gameplay remain unported. Use a test world and keep a backup; do not @@ -177,6 +177,23 @@ surface decoration in Flat. Void continues to generate no floor or biome decoration. The operator-only `age create_multi` probe creates two-biome test Ages for the dedicated-server smoke test. +The local `1.0.1.37-beta.1` slice adds the Cave terrain page to Binder +authoring. It uses Minecraft's caves noise settings, accepts Cave and Nether +biome pages (including multi-biome distribution), and searches below Y=64 for +a safe arrival instead of landing on the roof. The 26.3 vanilla Cave preset +defaults to water below Y=32, so this port derives a dry Cave setting with +air as its default fluid and the sea level below the world floor. This applies +to newly generated Cave chunks; it does not drain existing chunks. Cave-specific Nether surface +materials from 1.21.1 are not ported yet. The dedicated-server smoke now +creates and reloads Cave Ages alongside the other terrain families. + +The dry-Cave follow-up is packaged as `1.0.1.38-beta.1`; no 26.3 release is +published until the in-game check passes and publication is explicitly requested. + +The local `1.0.1.39-beta.1` follow-up rejects fluid at both feet and head +height during safe-landing searches. It also refuses to carve the fallback +platform into submerged space if no dry footing can be found. + From the repository root: ```sh diff --git a/ports/mc263/RELEASE_NOTES_1.0.1.39-beta.1.md b/ports/mc263/RELEASE_NOTES_1.0.1.39-beta.1.md new file mode 100644 index 0000000..62ac7bb --- /dev/null +++ b/ports/mc263/RELEASE_NOTES_1.0.1.39-beta.1.md @@ -0,0 +1,14 @@ +# Rivencraft 26.3 beta 1.0.1.39-beta.1 + +This beta adds Cave terrain to authored Ages on Minecraft 26.3. + +- Bind Descriptive Books with the Cave terrain page and Cave or Nether biome pages, including supported multi-biome distributions. +- Cave Ages use the cave noise shape but remain dry by default. Newly generated chunks no longer flood below the vanilla cave sea level. +- Cave travel searches from Y=64 downward for dry, properly footed space instead of landing on the cave roof or underwater. +- Expanded dedicated-server checks for Cave creation, biome distribution, and persistence after restart. + +The player verified the Cave and safe-landing changes in game. Existing generated chunks are not drained or regenerated. A future Flooded page will provide an authored water level; see [issue #107](https://git.paddedcell.org/dnorberg/rivencraft/issues/107). + +This 26.3 port is still experimental and does not yet include all features of the separate Minecraft 1.21.1 version. Use a test world and keep a backup. Minecraft 1.21.1 saves are not supported by this beta. + +Requires Minecraft 26.3, NeoForge 26.3.0.99 or a compatible 26.3 build, and Java 25. diff --git a/ports/mc263/gradle.properties b/ports/mc263/gradle.properties index d1b5cf3..9c70723 100644 --- a/ports/mc263/gradle.properties +++ b/ports/mc263/gradle.properties @@ -7,4 +7,4 @@ neo_version=26.3.0.99 mod_id=rivencraft mod_name=Rivencraft mod_license=LGPL-3.0-or-later -mod_version=1.0.1.36-beta.1 +mod_version=1.0.1.39-beta.1 diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeCommands.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeCommands.java index 02b43a3..47576d9 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeCommands.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeCommands.java @@ -78,7 +78,7 @@ public final class PortAgeCommands { context, "terrain").toUpperCase(Locale.ROOT)); } catch (IllegalArgumentException exception) { context.getSource().sendFailure(Component.literal( - "Unknown terrain; use standard, amplified, island, flat, or void")); + "Unknown terrain; use standard, amplified, island, cave, flat, or void")); return 0; } return createAge(context, terrain); @@ -94,18 +94,23 @@ public final class PortAgeCommands { context, "terrain").toUpperCase(Locale.ROOT)); } catch (IllegalArgumentException exception) { context.getSource().sendFailure(Component.literal( - "Unknown terrain; use flat or void")); + "Unknown terrain; use cave, flat, or void")); return 0; } - if (terrain != PortAgeTerrain.FLAT && terrain != PortAgeTerrain.VOID) { + if (terrain != PortAgeTerrain.CAVE + && terrain != PortAgeTerrain.FLAT + && terrain != PortAgeTerrain.VOID) { context.getSource().sendFailure(Component.literal( - "Multi-biome probe supports flat or void only")); + "Multi-biome probe supports cave, flat, or void only")); return 0; } return createAge(context, terrain, new PortBiomePlan( - PortBiomePlan.Distribution.TILED, java.util.List.of( - Identifier.withDefaultNamespace("plains"), - Identifier.withDefaultNamespace("forest")))); + PortBiomePlan.Distribution.TILED, + terrain == PortAgeTerrain.CAVE + ? java.util.List.of(Identifier.withDefaultNamespace("lush_caves"), + Identifier.withDefaultNamespace("dripstone_caves")) + : java.util.List.of(Identifier.withDefaultNamespace("plains"), + Identifier.withDefaultNamespace("forest")))); }))))) .then(Commands.literal("list") .executes(context -> { diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeTerrain.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeTerrain.java index 2aec3ae..c9a2d1a 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeTerrain.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeTerrain.java @@ -5,6 +5,7 @@ public enum PortAgeTerrain { STANDARD, AMPLIFIED, ISLAND, + CAVE, FLAT, VOID } diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeTravel.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeTravel.java index 2be87a2..84d77b2 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeTravel.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/PortAgeTravel.java @@ -33,6 +33,27 @@ public final class PortAgeTravel { } private static BlockPos findLanding(ServerLevel target) { + if (target.getChunkSource().getGenerator() instanceof net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator noise + && noise.generatorSettings().value().noiseSettings().equals(target.registryAccess() + .lookupOrThrow(Registries.NOISE_SETTINGS) + .getOrThrow(net.minecraft.world.level.levelgen.NoiseGeneratorSettings.CAVES) + .value().noiseSettings())) { + // The cave roof is not a valid arrival surface. Search down from + // Legacy's Y=64 start, then construct the normal safety platform + // at that height only when no footed opening can be found. + for (int radius = 0; radius <= 16; radius++) { + for (int x = -radius; x <= radius; x++) { + for (int z = -radius; z <= radius; z++) { + if (Math.max(Math.abs(x), Math.abs(z)) != radius) continue; + for (int y = Math.min(64, target.getMaxY() - 2); y > target.getMinY(); y--) { + BlockPos foot = new BlockPos(x, y, z); + if (hasFooting(target, foot)) return foot; + } + } + } + } + return makePlatform(target, new BlockPos(0, 64, 0)); + } // A Flat Age has a known surface. Use it directly before a heightmap // fallback can place a platform in the air above a low superflat world. FlatLevelGeneratorSettings flatSettings = switch (target.getChunkSource().getGenerator()) { @@ -62,10 +83,21 @@ public final class PortAgeTravel { // origin is ocean or foliage. Final book landing rules are ported later. int y = Math.max(64, target.getHeight(Heightmap.Types.MOTION_BLOCKING, 0, 0) + 2); if (y + 2 >= target.getMaxY()) return null; - BlockPos foot = new BlockPos(0, y, 0); + return makePlatform(target, new BlockPos(0, y, 0)); + } + + private static BlockPos makePlatform(ServerLevel target, BlockPos foot) { + if (foot.getY() <= target.getMinY() || foot.getY() + 2 >= target.getMaxY()) return null; + // A carved pocket inside a flooded cave immediately refills. Refuse + // that landing instead of placing the player in submerged spawn space. + if (!target.getFluidState(foot).isEmpty() || !target.getFluidState(foot.above()).isEmpty()) return null; + for (Direction direction : Direction.Plane.HORIZONTAL) { + if (!target.getFluidState(foot.relative(direction)).isEmpty() + || !target.getFluidState(foot.above().relative(direction)).isEmpty()) return null; + } for (int x = -1; x <= 1; x++) { for (int z = -1; z <= 1; z++) { - target.setBlock(new BlockPos(x, y - 1, z), Rivencraft.CRYSTAL_BLOCK.get().defaultBlockState(), 3); + target.setBlock(foot.offset(x, -1, z), Rivencraft.CRYSTAL_BLOCK.get().defaultBlockState(), 3); } } target.setBlock(foot, Blocks.AIR.defaultBlockState(), 3); @@ -76,6 +108,8 @@ public final class PortAgeTravel { private static boolean hasFooting(ServerLevel target, BlockPos foot) { BlockPos floor = foot.below(); return target.getBlockState(floor).isFaceSturdy(target, floor, Direction.UP) + && target.getFluidState(foot).isEmpty() + && target.getFluidState(foot.above()).isEmpty() && target.getBlockState(foot).getCollisionShape(target, foot).isEmpty() && target.getBlockState(foot.above()).getCollisionShape(target, foot.above()).isEmpty(); } diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/RuntimeAgeBackend.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/RuntimeAgeBackend.java index ea455fa..f2f1501 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/RuntimeAgeBackend.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/age/RuntimeAgeBackend.java @@ -11,6 +11,7 @@ import java.util.Map; import java.util.Optional; import java.util.OptionalLong; import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.writing.PortRegistryPages; import net.minecraft.core.registries.Registries; import net.minecraft.core.Holder; import net.minecraft.core.HolderSet; @@ -325,7 +326,19 @@ public final class RuntimeAgeBackend { || terrain != PortAgeTerrain.STANDARD) { if (!(generator instanceof NoiseBasedChunkGenerator noise)) throw new IllegalStateException("Overworld generator cannot accept authored noise settings"); - var source = generator.getBiomeSource(); + var source = terrain == PortAgeTerrain.CAVE + ? new FixedBiomeSource(server.registryAccess().lookupOrThrow(Registries.BIOME) + .getOrThrow(Biomes.LUSH_CAVES)) + : generator.getBiomeSource(); + if (terrain == PortAgeTerrain.CAVE && biomePlan != null) { + var biomes = server.registryAccess().lookupOrThrow(Registries.BIOME); + for (Identifier biomeId : biomePlan.biomes()) { + var holder = biomes.get(ResourceKey.create(Registries.BIOME, biomeId)) + .orElseThrow(() -> new IllegalArgumentException("Unknown authored biome: " + biomeId)); + if (!PortRegistryPages.isCaveBiome(holder)) + throw new IllegalArgumentException("Cave terrain requires a Cave or Nether biome: " + biomeId); + } + } if (biomePlan != null && biomePlan.distribution() != PortBiomePlan.Distribution.NATIVE) { source = authoredBiomeSource(server, biomePlan, seed); } @@ -335,6 +348,7 @@ public final class RuntimeAgeBackend { .getOrThrow(NoiseGeneratorSettings.AMPLIFIED); case ISLAND -> server.registryAccess().lookupOrThrow(Registries.NOISE_SETTINGS) .getOrThrow(NoiseGeneratorSettings.FLOATING_ISLANDS); + case CAVE -> dryCaveSettings(server); case FLAT, VOID -> throw new IllegalStateException("Flat terrain should use FlatLevelSource"); }; generator = new NoiseBasedChunkGenerator(source, settings); @@ -369,4 +383,14 @@ public final class RuntimeAgeBackend { throw exception; } } + + private static Holder dryCaveSettings(MinecraftServer server) { + NoiseGeneratorSettings caves = server.registryAccess().lookupOrThrow(Registries.NOISE_SETTINGS) + .getOrThrow(NoiseGeneratorSettings.CAVES).value(); + return Holder.direct(new NoiseGeneratorSettings( + caves.noiseSettings(), caves.defaultBlock(), Blocks.AIR.defaultBlockState(), + caves.noiseRouter(), caves.materialRule(), caves.spawnTarget(), + caves.noiseSettings().minY() - 1, caves.disableMobGeneration(), Optional.empty(), + caves.useLegacyRandomSource(), caves.debugFunctions())); + } } diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortAuthoredWriting.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortAuthoredWriting.java index bd71b11..88aae28 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortAuthoredWriting.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortAuthoredWriting.java @@ -23,6 +23,8 @@ public final class PortAuthoredWriting { Rivencraft.MOD_ID, "terrain/flat"); private static final Identifier VOID_TERRAIN = Identifier.fromNamespaceAndPath( Rivencraft.MOD_ID, "terrain/void"); + private static final Identifier CAVE_TERRAIN = Identifier.fromNamespaceAndPath( + Rivencraft.MOD_ID, "terrain/cave"); private PortAuthoredWriting() {} @@ -60,7 +62,7 @@ public final class PortAuthoredWriting { private static boolean isTerrain(Identifier page) { return page.equals(STANDARD_TERRAIN) || page.equals(AMPLIFIED_TERRAIN) || page.equals(ISLAND_TERRAIN) || page.equals(FLAT_TERRAIN) - || page.equals(VOID_TERRAIN); + || page.equals(VOID_TERRAIN) || page.equals(CAVE_TERRAIN); } private static PortBiomePlan.Distribution controller(Identifier page) { @@ -85,6 +87,7 @@ public final class PortAuthoredWriting { if (pages.contains(ISLAND_TERRAIN)) return PortAgeTerrain.ISLAND; if (pages.contains(FLAT_TERRAIN)) return PortAgeTerrain.FLAT; if (pages.contains(VOID_TERRAIN)) return PortAgeTerrain.VOID; + if (pages.contains(CAVE_TERRAIN)) return PortAgeTerrain.CAVE; return PortAgeTerrain.STANDARD; } @@ -94,14 +97,17 @@ public final class PortAuthoredWriting { "Unsupported terrain or biome-page combination in this port slice"); List biomes = new ArrayList<>(); PortBiomePlan.Distribution distribution = null; + boolean caveTerrain = pages.contains(CAVE_TERRAIN); for (Identifier page : pages) { if (isTerrain(page)) continue; PortBiomePlan.Distribution selected = controller(page); if (selected != null) { distribution = selected; continue; } Identifier biome = PortRegistryPages.biomeId(page); var registry = registries.lookupOrThrow(Registries.BIOME); - if (registry.get(ResourceKey.create(Registries.BIOME, biome)).isEmpty()) - throw new IllegalArgumentException("Unknown authored biome: " + biome); + var holder = registry.get(ResourceKey.create(Registries.BIOME, biome)) + .orElseThrow(() -> new IllegalArgumentException("Unknown authored biome: " + biome)); + if (caveTerrain && !PortRegistryPages.isCaveBiome(holder)) + throw new IllegalArgumentException("Cave terrain requires a Cave or Nether biome: " + biome); biomes.add(biome); } if (distribution == PortBiomePlan.Distribution.NATIVE) diff --git a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortRegistryPages.java b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortRegistryPages.java index 3303def..c6faced 100644 --- a/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortRegistryPages.java +++ b/ports/mc263/src/main/java/net/dnorberg/rivencraft/writing/PortRegistryPages.java @@ -67,7 +67,7 @@ public final class PortRegistryPages { public static List biomePages(HolderLookup.Provider registries, boolean caves) { return registries.lookupOrThrow(Registries.BIOME).listElements() .filter(holder -> !holder.key().identifier().equals(DEFERRED_PLACEHOLDER)) - .filter(holder -> isCave(holder) == caves) + .filter(holder -> isCaveBiome(holder) == caves) .map(holder -> PageItem.createSymbolPage(biomePageId(holder.key().identifier()))) .sorted((left, right) -> left.get(Rivencraft.SYMBOL_PAGE_ID.get()).toString() .compareTo(right.get(Rivencraft.SYMBOL_PAGE_ID.get()).toString())) @@ -81,7 +81,7 @@ public final class PortRegistryPages { .map(id -> PageItem.createSymbolPage(blockPageId(id))).toList(); } - private static boolean isCave(net.minecraft.core.Holder.Reference holder) { + public static boolean isCaveBiome(net.minecraft.core.Holder.Reference holder) { String path = holder.key().identifier().getPath(); return holder.is(CAVE_BIOMES) || holder.is(BiomeTags.IS_NETHER) || path.equals("deep_dark") || path.endsWith("_caves"); diff --git a/ports/mc263/src/main/resources/data/rivencraft/tags/worldgen/biome/cave_biomes.json b/ports/mc263/src/main/resources/data/rivencraft/tags/worldgen/biome/cave_biomes.json new file mode 100644 index 0000000..1f0ddfb --- /dev/null +++ b/ports/mc263/src/main/resources/data/rivencraft/tags/worldgen/biome/cave_biomes.json @@ -0,0 +1,8 @@ +{ + "replace": false, + "values": [ + "minecraft:lush_caves", + "minecraft:dripstone_caves", + "minecraft:deep_dark" + ] +} diff --git a/scripts/smoke_server_263.sh b/scripts/smoke_server_263.sh index d88659c..538b71a 100755 --- a/scripts/smoke_server_263.sh +++ b/scripts/smoke_server_263.sh @@ -71,8 +71,10 @@ send_and_wait 'rivencraft_port age create smoke_alpha 123456789' 'Created rivenc send_and_wait 'rivencraft_port age create smoke_beta 987654321' 'Created rivencraft:age/smoke_beta with seed 987654321' send_and_wait 'rivencraft_port age create smoke_flat 24680 flat' 'Created rivencraft:age/smoke_flat with seed 24680 terrain=FLAT' send_and_wait 'rivencraft_port age create smoke_void 13579 void' 'Created rivencraft:age/smoke_void with seed 13579 terrain=VOID' +send_and_wait 'rivencraft_port age create smoke_cave 112233 cave' 'Created rivencraft:age/smoke_cave with seed 112233 terrain=CAVE' send_and_wait 'rivencraft_port age create_multi smoke_flat_multi 24681 flat' 'Created rivencraft:age/smoke_flat_multi with seed 24681 terrain=FLAT' send_and_wait 'rivencraft_port age create_multi smoke_void_multi 13580 void' 'Created rivencraft:age/smoke_void_multi with seed 13580 terrain=VOID' +send_and_wait 'rivencraft_port age create_multi smoke_cave_multi 112234 cave' 'Created rivencraft:age/smoke_cave_multi with seed 112234 terrain=CAVE' # The index must reach disk while the server is still running. A graceful # shutdown would hide the interrupted-save regression this checks. index_file="$server_dir/world/dimensions/minecraft/overworld/data/rivencraft/runtime_ages.dat" @@ -84,18 +86,24 @@ gzip -dc "$index_file" | strings | grep -Fq 'rivencraft:age/smoke_alpha' gzip -dc "$index_file" | strings | grep -Fq 'rivencraft:age/smoke_beta' gzip -dc "$index_file" | strings | grep -Fq 'rivencraft:age/smoke_flat' gzip -dc "$index_file" | strings | grep -Fq 'rivencraft:age/smoke_void' +gzip -dc "$index_file" | strings | grep -Fq 'rivencraft:age/smoke_cave' gzip -dc "$index_file" | strings | grep -Fq 'rivencraft:age/smoke_flat_multi' gzip -dc "$index_file" | strings | grep -Fq 'rivencraft:age/smoke_void_multi' +gzip -dc "$index_file" | strings | grep -Fq 'rivencraft:age/smoke_cave_multi' send_and_wait 'rivencraft_port age sample smoke_alpha' 'rivencraft:age/smoke_alpha seed=123456789 chunkOffset=0 terrain=' send_and_wait 'rivencraft_port age sample smoke_beta' 'rivencraft:age/smoke_beta seed=987654321 chunkOffset=0 terrain=' send_and_wait 'rivencraft_port age sample smoke_flat' 'rivencraft:age/smoke_flat seed=24680 chunkOffset=0 terrain=' send_and_wait 'rivencraft_port age sample smoke_void' 'rivencraft:age/smoke_void seed=13579 chunkOffset=0 terrain=' +send_and_wait 'rivencraft_port age sample smoke_cave' 'rivencraft:age/smoke_cave seed=112233 chunkOffset=0 terrain=' send_and_wait 'rivencraft_port age sample smoke_flat_multi' 'rivencraft:age/smoke_flat_multi seed=24681 chunkOffset=0 terrain=' send_and_wait 'rivencraft_port age sample smoke_void_multi' 'rivencraft:age/smoke_void_multi seed=13580 chunkOffset=0 terrain=' +send_and_wait 'rivencraft_port age sample smoke_cave_multi' 'rivencraft:age/smoke_cave_multi seed=112234 chunkOffset=0 terrain=' send_and_wait 'execute in rivencraft:age/smoke_flat run forceload add 0 0' 'Marked chunk [0, 0] in rivencraft:age/smoke_flat to be force loaded' send_and_wait 'execute in rivencraft:age/smoke_void run forceload add 0 0' 'Marked chunk [0, 0] in rivencraft:age/smoke_void to be force loaded' send_and_wait 'execute in rivencraft:age/smoke_flat_multi run forceload add 0 0' 'Marked chunk [0, 0] in rivencraft:age/smoke_flat_multi to be force loaded' send_and_wait 'execute in rivencraft:age/smoke_void_multi run forceload add 0 0' 'Marked chunk [0, 0] in rivencraft:age/smoke_void_multi to be force loaded' +send_and_wait 'execute in rivencraft:age/smoke_cave run forceload add 0 0' 'Marked chunk [0, 0] in rivencraft:age/smoke_cave to be force loaded' +send_and_wait 'execute in rivencraft:age/smoke_cave_multi run forceload add 0 0' 'Marked chunk [0, 0] in rivencraft:age/smoke_cave_multi to be force loaded' send_and_wait 'execute in rivencraft:age/smoke_flat if block 0 0 0 minecraft:bedrock run say Flat bedrock starts at Y0' 'Flat bedrock starts at Y0' send_and_wait 'execute in rivencraft:age/smoke_flat if block 0 3 0 minecraft:grass_block run say Flat surface is Y3' 'Flat surface is Y3' send_and_wait 'execute in rivencraft:age/smoke_void if block 0 0 0 minecraft:air run say Void remains empty' 'Void remains empty' @@ -105,6 +113,8 @@ send_and_wait 'execute in rivencraft:age/smoke_void_multi if block 0 0 0 minecra send_and_wait 'rivencraft_port age biome smoke_flat_multi 128 128' 'rivencraft:age/smoke_flat_multi biome at 128,128=minecraft:plains' send_and_wait 'rivencraft_port age biome smoke_flat_multi 384 128' 'rivencraft:age/smoke_flat_multi biome at 384,128=minecraft:forest' send_and_wait 'rivencraft_port age biome smoke_void_multi 384 128' 'rivencraft:age/smoke_void_multi biome at 384,128=minecraft:forest' +send_and_wait 'rivencraft_port age biome smoke_cave_multi 128 128' 'rivencraft:age/smoke_cave_multi biome at 128,128=minecraft:lush_caves' +send_and_wait 'rivencraft_port age biome smoke_cave_multi 384 128' 'rivencraft:age/smoke_cave_multi biome at 384,128=minecraft:dripstone_caves' alpha_before=$(grep -F 'rivencraft:age/smoke_alpha seed=123456789 chunkOffset=0 terrain=' "$log_file" | tail -1 | sed 's/.*terrain=//') beta_before=$(grep -F 'rivencraft:age/smoke_beta seed=987654321 chunkOffset=0 terrain=' "$log_file" | tail -1 | sed 's/.*terrain=//') flat_before=$(grep -F 'rivencraft:age/smoke_flat seed=24680 chunkOffset=0 terrain=' "$log_file" | tail -1 | sed 's/.*terrain=//') @@ -125,10 +135,13 @@ send_and_wait 'rivencraft_port age sample smoke_alpha' 'rivencraft:age/smoke_alp send_and_wait 'rivencraft_port age sample smoke_beta' 'rivencraft:age/smoke_beta seed=987654321 chunkOffset=0 terrain=' send_and_wait 'rivencraft_port age sample smoke_flat' 'rivencraft:age/smoke_flat seed=24680 chunkOffset=0 terrain=' send_and_wait 'rivencraft_port age sample smoke_void' 'rivencraft:age/smoke_void seed=13579 chunkOffset=0 terrain=' +send_and_wait 'rivencraft_port age sample smoke_cave' 'rivencraft:age/smoke_cave seed=112233 chunkOffset=0 terrain=' send_and_wait 'rivencraft_port age sample smoke_flat_multi' 'rivencraft:age/smoke_flat_multi seed=24681 chunkOffset=0 terrain=' send_and_wait 'rivencraft_port age sample smoke_void_multi' 'rivencraft:age/smoke_void_multi seed=13580 chunkOffset=0 terrain=' +send_and_wait 'rivencraft_port age sample smoke_cave_multi' 'rivencraft:age/smoke_cave_multi seed=112234 chunkOffset=0 terrain=' send_and_wait 'rivencraft_port age biome smoke_flat_multi 384 128' 'rivencraft:age/smoke_flat_multi biome at 384,128=minecraft:forest' send_and_wait 'rivencraft_port age biome smoke_void_multi 384 128' 'rivencraft:age/smoke_void_multi biome at 384,128=minecraft:forest' +send_and_wait 'rivencraft_port age biome smoke_cave_multi 384 128' 'rivencraft:age/smoke_cave_multi biome at 384,128=minecraft:dripstone_caves' alpha_after=$(grep -F 'rivencraft:age/smoke_alpha seed=123456789 chunkOffset=0 terrain=' "$log_file" | tail -1 | sed 's/.*terrain=//') beta_after=$(grep -F 'rivencraft:age/smoke_beta seed=987654321 chunkOffset=0 terrain=' "$log_file" | tail -1 | sed 's/.*terrain=//') flat_after=$(grep -F 'rivencraft:age/smoke_flat seed=24680 chunkOffset=0 terrain=' "$log_file" | tail -1 | sed 's/.*terrain=//') @@ -144,4 +157,4 @@ void_after=$(grep -F 'rivencraft:age/smoke_void seed=13579 chunkOffset=0 terrain exit 1 } stop_server -printf '26.3 dedicated-server smoke passed: Standard, Flat and Void Ages persisted across restart.\n' +printf '26.3 dedicated-server smoke passed: Standard, Flat, Void and Cave Ages persisted across restart.\n'