Public Access
Port Flat and Void Age terrain to Minecraft 26.3 beta
This commit is contained in:
1 parent
cb31156584
commit
5546448033
13 files changed
+454
-48
No files matched your search
@@ -480,6 +480,21 @@ symbol-page data and authoring pipeline, not just drawing more placeholders.
|
||||
The local `1.0.1.15-alpha.1` follow-up changes the missing-panel cue to the
|
||||
Link Panel's 16×16 item icon while preserving its red pulse and tooltip.
|
||||
|
||||
The local `1.0.1.33-beta.1` slice adds Flat and Void terrain to authored Binder
|
||||
books. Flat uses vanilla superflat layers and can use one authored Biome; Void
|
||||
generates no terrain but can use one authored Biome for environmental behavior.
|
||||
Without a Biome page, Void uses `minecraft:the_void`. Books with multiple Biome
|
||||
pages and Flat/Void terrain remain unassembled until their custom distribution
|
||||
generator is ported. The dedicated-server smoke creates, samples, and reloads
|
||||
both terrain families in a disposable world.
|
||||
|
||||
The local `1.0.1.34-beta.1` follow-up moves Flat ground to Y=0 and targets its
|
||||
surface for travel. `1.0.1.35-beta.1` enables the selected biome's decoration
|
||||
on Flat. `1.0.1.36-beta.1` ports a multi-biome Flat/Void chunk generator and
|
||||
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.
|
||||
|
||||
## Verification of this first slice
|
||||
|
||||
The 1.21.1 `compileJava` and `build` tasks passed offline after the backend
|
||||
|
||||
+26
-2
@@ -3,8 +3,9 @@
|
||||
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, or Island terrain with supported biome-distribution
|
||||
pages. Other terrain families, feature/material effects, portals, and much of
|
||||
author Standard, Amplified, Island, Flat, or Void 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
|
||||
substitute this JAR for the 1.21.1 mod or import 1.21.1 saves.
|
||||
|
||||
@@ -153,6 +154,29 @@ terrain; and saved terrain/biome plans across reload. A Binder or Ink Mixer
|
||||
beside another block no longer hides that block's faces. Unsupported authoring
|
||||
still stays in the Binder rather than silently producing an incorrect Age.
|
||||
|
||||
The local `1.0.1.33-beta.1` slice adds Flat and Void terrain to Binder
|
||||
authoring. Flat uses vanilla's superflat layers; Void has no generated floor
|
||||
and uses the selected Biome for its environmental rules. Each accepts zero or
|
||||
one authored Biome page. Unsupported multi-biome Flat/Void books remain in the
|
||||
Binder until their custom generator is ported. The operator probe can create
|
||||
test Ages with `rivencraft_port age create <name> <seed> flat|void`.
|
||||
|
||||
The local `1.0.1.34-beta.1` follow-up starts Flat's solid layers at Y=0
|
||||
(bedrock at 0, dirt at 1–2, grass at 3) and lands players at the ground surface
|
||||
instead of creating an elevated fallback platform. This affects newly
|
||||
generated Flat chunks; previously generated Age chunks are not rewritten.
|
||||
|
||||
The local `1.0.1.35-beta.1` follow-up enables biome decoration on newly
|
||||
generated Flat chunks, allowing the selected biome's trees, vegetation, and
|
||||
other placed features to appear on the Flat surface. Void remains undecorated.
|
||||
|
||||
The local `1.0.1.36-beta.1` slice accepts multiple Biome pages with Flat and
|
||||
Void terrain. It ports the 1.21.1 authored-biome Flat generator to 26.3, so
|
||||
Tiled, Grid, and scaled regions affect climate in both terrain types and
|
||||
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.
|
||||
|
||||
From the repository root:
|
||||
|
||||
```sh
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Rivencraft 26.3 beta 1.0.1.36-beta.1
|
||||
|
||||
This beta adds Flat and Void terrain to authored Ages on Minecraft 26.3.
|
||||
|
||||
- Bind Descriptive Books with Flat or Void terrain, including multiple Biome pages and supported biome-distribution pages.
|
||||
- Flat ground starts at Y=0; travel lands on its surface rather than on an elevated fallback platform.
|
||||
- Flat Ages generate the selected biomes' surface decorations, including trees and vegetation. Void Ages remain empty while retaining authored biome climate.
|
||||
- Expanded dedicated-server checks for Flat and Void terrain, biome distribution, and persistence after restart.
|
||||
|
||||
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.
|
||||
@@ -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.32-beta.1
|
||||
mod_version=1.0.1.36-beta.1
|
||||
@@ -21,6 +21,7 @@ import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.network.PortBookNetworking;
|
||||
import net.dnorberg.rivencraft.registry.ModCreativeTabs;
|
||||
import net.dnorberg.rivencraft.registry.PortBiomeSources;
|
||||
import net.dnorberg.rivencraft.registry.PortChunkGenerators;
|
||||
import net.dnorberg.rivencraft.writing.LinkingBookRecipe;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
@@ -152,6 +153,7 @@ public final class Rivencraft {
|
||||
RECIPE_SERIALIZERS.register(modEventBus);
|
||||
ModCreativeTabs.register(modEventBus);
|
||||
PortBiomeSources.register(modEventBus);
|
||||
PortChunkGenerators.register(modEventBus);
|
||||
NeoForge.EVENT_BUS.addListener(RuntimeAgeBackend::onLevelLoad);
|
||||
NeoForge.EVENT_BUS.addListener(PortAgeCommands::register);
|
||||
NeoForge.EVENT_BUS.addListener(AgeCatalogSync::onLogin);
|
||||
|
||||
@@ -3,9 +3,11 @@ package net.dnorberg.rivencraft.age;
|
||||
import com.mojang.brigadier.arguments.LongArgumentType;
|
||||
import com.mojang.brigadier.arguments.IntegerArgumentType;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.item.LinkBookTarget;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -20,6 +22,7 @@ import net.neoforged.neoforge.event.RegisterCommandsEvent;
|
||||
import net.neoforged.fml.ModList;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.Locale;
|
||||
|
||||
/** Operator-only backend and synchronized-travel probes, not normal book behavior. */
|
||||
public final class PortAgeCommands {
|
||||
@@ -66,23 +69,44 @@ public final class PortAgeCommands {
|
||||
.then(Commands.literal("create")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.then(Commands.argument("seed", LongArgumentType.longArg())
|
||||
.executes(context -> {
|
||||
String name = StringArgumentType.getString(context, "name");
|
||||
Identifier id = Identifier.fromNamespaceAndPath(
|
||||
Rivencraft.MOD_ID, "age/" + name);
|
||||
long seed = LongArgumentType.getLong(context, "seed");
|
||||
ServerLevel level;
|
||||
try {
|
||||
level = RuntimeAgeBackend.create(
|
||||
context.getSource().getServer(), id, seed);
|
||||
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||
context.getSource().sendFailure(Component.literal(exception.getMessage()));
|
||||
return 0;
|
||||
}
|
||||
context.getSource().sendSuccess(() -> Component.literal(
|
||||
"Created " + id + " with seed " + level.getSeed()), true);
|
||||
return 1;
|
||||
}))))
|
||||
.executes(context -> createAge(context, PortAgeTerrain.STANDARD))
|
||||
.then(Commands.argument("terrain", StringArgumentType.word())
|
||||
.executes(context -> {
|
||||
PortAgeTerrain terrain;
|
||||
try {
|
||||
terrain = PortAgeTerrain.valueOf(StringArgumentType.getString(
|
||||
context, "terrain").toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException exception) {
|
||||
context.getSource().sendFailure(Component.literal(
|
||||
"Unknown terrain; use standard, amplified, island, flat, or void"));
|
||||
return 0;
|
||||
}
|
||||
return createAge(context, terrain);
|
||||
})))))
|
||||
.then(Commands.literal("create_multi")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.then(Commands.argument("seed", LongArgumentType.longArg())
|
||||
.then(Commands.argument("terrain", StringArgumentType.word())
|
||||
.executes(context -> {
|
||||
PortAgeTerrain terrain;
|
||||
try {
|
||||
terrain = PortAgeTerrain.valueOf(StringArgumentType.getString(
|
||||
context, "terrain").toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException exception) {
|
||||
context.getSource().sendFailure(Component.literal(
|
||||
"Unknown terrain; use flat or void"));
|
||||
return 0;
|
||||
}
|
||||
if (terrain != PortAgeTerrain.FLAT && terrain != PortAgeTerrain.VOID) {
|
||||
context.getSource().sendFailure(Component.literal(
|
||||
"Multi-biome probe supports 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"))));
|
||||
})))))
|
||||
.then(Commands.literal("list")
|
||||
.executes(context -> {
|
||||
var ages = RuntimeAgeBackend.savedAges(context.getSource().getServer());
|
||||
@@ -185,7 +209,33 @@ public final class PortAgeCommands {
|
||||
.executes(context -> sampleAge(context, 0))
|
||||
.then(Commands.argument("chunkOffset", IntegerArgumentType.integer(0))
|
||||
.executes(context -> sampleAge(context,
|
||||
IntegerArgumentType.getInteger(context, "chunkOffset"))))))));
|
||||
IntegerArgumentType.getInteger(context, "chunkOffset"))))))
|
||||
.then(Commands.literal("biome")
|
||||
.then(Commands.argument("name", StringArgumentType.word())
|
||||
.then(Commands.argument("x", IntegerArgumentType.integer())
|
||||
.then(Commands.argument("z", IntegerArgumentType.integer())
|
||||
.executes(PortAgeCommands::sampleBiome)))))));
|
||||
}
|
||||
|
||||
private static int createAge(CommandContext<CommandSourceStack> context, PortAgeTerrain terrain) {
|
||||
return createAge(context, terrain, null);
|
||||
}
|
||||
|
||||
private static int createAge(CommandContext<CommandSourceStack> context,
|
||||
PortAgeTerrain terrain, PortBiomePlan biomePlan) {
|
||||
String name = StringArgumentType.getString(context, "name");
|
||||
Identifier id = Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID, "age/" + name);
|
||||
long seed = LongArgumentType.getLong(context, "seed");
|
||||
ServerLevel level;
|
||||
try {
|
||||
level = RuntimeAgeBackend.create(context.getSource().getServer(), id, seed, biomePlan, terrain);
|
||||
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||
context.getSource().sendFailure(Component.literal(exception.getMessage()));
|
||||
return 0;
|
||||
}
|
||||
context.getSource().sendSuccess(() -> Component.literal(
|
||||
"Created " + id + " with seed " + level.getSeed() + " terrain=" + terrain), true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int sampleAge(com.mojang.brigadier.context.CommandContext<net.minecraft.commands.CommandSourceStack> context,
|
||||
@@ -213,6 +263,23 @@ public final class PortAgeCommands {
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static int sampleBiome(CommandContext<CommandSourceStack> context) {
|
||||
Identifier id = ageId(context);
|
||||
ServerLevel level = context.getSource().getServer().getLevel(ResourceKey.create(Registries.DIMENSION, id));
|
||||
if (level == null) {
|
||||
context.getSource().sendFailure(Component.literal("Age not loaded: " + id));
|
||||
return 0;
|
||||
}
|
||||
int x = IntegerArgumentType.getInteger(context, "x");
|
||||
int z = IntegerArgumentType.getInteger(context, "z");
|
||||
level.getChunk(x >> 4, z >> 4);
|
||||
Identifier biome = level.getBiome(new net.minecraft.core.BlockPos(x, 4, z))
|
||||
.unwrapKey().orElseThrow().identifier();
|
||||
context.getSource().sendSuccess(() -> Component.literal(
|
||||
id + " biome at " + x + "," + z + "=" + biome), false);
|
||||
return 1;
|
||||
}
|
||||
|
||||
private static Identifier ageId(com.mojang.brigadier.context.CommandContext<net.minecraft.commands.CommandSourceStack> context) {
|
||||
return Identifier.fromNamespaceAndPath(Rivencraft.MOD_ID,
|
||||
"age/" + StringArgumentType.getString(context, "name"));
|
||||
|
||||
@@ -4,5 +4,7 @@ package net.dnorberg.rivencraft.age;
|
||||
public enum PortAgeTerrain {
|
||||
STANDARD,
|
||||
AMPLIFIED,
|
||||
ISLAND
|
||||
ISLAND,
|
||||
FLAT,
|
||||
VOID
|
||||
}
|
||||
@@ -11,6 +11,8 @@ import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||
|
||||
/** Shared, server-authoritative landing for the 26.3 book and operator probe. */
|
||||
public final class PortAgeTravel {
|
||||
@@ -31,16 +33,26 @@ public final class PortAgeTravel {
|
||||
}
|
||||
|
||||
private static BlockPos findLanding(ServerLevel target) {
|
||||
// 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()) {
|
||||
case FlatLevelSource flat -> flat.settings();
|
||||
case PortFlatChunkGenerator flat -> flat.settings();
|
||||
default -> null;
|
||||
};
|
||||
if (flatSettings != null && !flatSettings.getLayersInfo().isEmpty()) {
|
||||
int surfaceY = target.getMinY() + flatSettings.getLayersInfo().stream()
|
||||
.mapToInt(layer -> layer.getHeight()).sum();
|
||||
BlockPos surface = new BlockPos(0, surfaceY, 0);
|
||||
if (hasFooting(target, surface)) return surface;
|
||||
}
|
||||
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;
|
||||
int y = target.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z);
|
||||
BlockPos foot = new BlockPos(x, y, z);
|
||||
BlockPos floor = foot.below();
|
||||
if (target.getBlockState(floor).isFaceSturdy(target, floor, Direction.UP)
|
||||
&& target.getBlockState(foot).getCollisionShape(target, foot).isEmpty()
|
||||
&& target.getBlockState(foot.above()).getCollisionShape(target, foot.above()).isEmpty()) {
|
||||
if (hasFooting(target, foot)) {
|
||||
return foot;
|
||||
}
|
||||
}
|
||||
@@ -60,4 +72,11 @@ public final class PortAgeTravel {
|
||||
target.setBlock(foot.above(), Blocks.AIR.defaultBlockState(), 3);
|
||||
return foot;
|
||||
}
|
||||
|
||||
private static boolean hasFooting(ServerLevel target, BlockPos foot) {
|
||||
BlockPos floor = foot.below();
|
||||
return target.getBlockState(floor).isFaceSturdy(target, floor, Direction.UP)
|
||||
&& target.getBlockState(foot).getCollisionShape(target, foot).isEmpty()
|
||||
&& target.getBlockState(foot.above()).getCollisionShape(target, foot.above()).isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package net.dnorberg.rivencraft.age;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Stream;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.server.level.WorldGenRegion;
|
||||
import net.minecraft.world.level.LevelHeightAccessor;
|
||||
import net.minecraft.world.level.NoiseColumn;
|
||||
import net.minecraft.world.level.StructureManager;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeGenerationSettings;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
|
||||
import net.minecraft.world.level.levelgen.GenerationStep;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.levelgen.RandomState;
|
||||
import net.minecraft.world.level.levelgen.blending.Blender;
|
||||
import net.minecraft.world.level.levelgen.densityfunction.SamplerContext;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
|
||||
import net.minecraft.world.level.levelgen.structure.StructureSet;
|
||||
import net.neoforged.neoforge.common.util.MemoizedFunction;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/** Flat layers with the authored biome distribution used by 1.21.1 Ages. */
|
||||
public final class PortFlatChunkGenerator extends ChunkGenerator {
|
||||
public static final MapCodec<PortFlatChunkGenerator> CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group(
|
||||
BiomeSource.CODEC.fieldOf("biome_source").forGetter(generator -> generator.biomeSource),
|
||||
FlatLevelGeneratorSettings.CODEC.fieldOf("settings").forGetter(PortFlatChunkGenerator::settings)
|
||||
).apply(instance, PortFlatChunkGenerator::new));
|
||||
|
||||
private final FlatLevelGeneratorSettings settings;
|
||||
private final MemoizedFunction<Holder<Biome>, BiomeGenerationSettings> generationSettingsGetter;
|
||||
|
||||
public PortFlatChunkGenerator(BiomeSource biomeSource, FlatLevelGeneratorSettings settings) {
|
||||
super(biomeSource, this.generationSettingsGetter = MemoizedFunction.of(
|
||||
biome -> generationSettings(settings, biome)));
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public FlatLevelGeneratorSettings settings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static BiomeGenerationSettings generationSettings(FlatLevelGeneratorSettings settings, Holder<Biome> biome) {
|
||||
// Void biomes still control climate, but must not place terrain features.
|
||||
if (settings.getLayersInfo().isEmpty()) return BiomeGenerationSettings.EMPTY;
|
||||
if (biome.equals(settings.getBiome())) return settings.adjustGenerationSettings(biome);
|
||||
BiomeGenerationSettings.PlainBuilder builder = new BiomeGenerationSettings.PlainBuilder();
|
||||
List<HolderSet<PlacedFeature>> features = biome.value().getGenerationSettings().features();
|
||||
for (int step = 0; step < features.size(); step++) {
|
||||
if (step == GenerationStep.Decoration.UNDERGROUND_STRUCTURES.ordinal()
|
||||
|| step == GenerationStep.Decoration.SURFACE_STRUCTURES.ordinal()) continue;
|
||||
for (Holder<PlacedFeature> feature : features.get(step)) builder.addFeature(step, feature);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MapCodec<? extends ChunkGenerator> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structures,
|
||||
RandomState random, long seed) {
|
||||
Stream<Holder<StructureSet>> entries = settings.structureOverrides()
|
||||
.map(HolderSet::stream)
|
||||
.orElseGet(() -> structures.listElements().map(holder -> (Holder<StructureSet>) holder));
|
||||
return ChunkGeneratorStructureState.createForFlat(random, seed, getOrigin(random), biomeSource, entries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpawnHeight(LevelHeightAccessor height) {
|
||||
return height.getMinY() + Math.min(height.getHeight(), settings.getLayers().size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ChunkAccess> buildTerrain(ChunkAccess chunk, Blender blender,
|
||||
RandomState random, StructureManager structures, BiomeManager biomes,
|
||||
@Nullable WorldGenRegion carverBiomeRegion, Set<Holder<Biome>> possibleBiomes) {
|
||||
List<BlockState> layers = settings.getLayers();
|
||||
BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
|
||||
Heightmap floor = chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR_WG);
|
||||
Heightmap surface = chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.WORLD_SURFACE_WG);
|
||||
for (int layer = 0; layer < Math.min(chunk.getHeight(), layers.size()); layer++) {
|
||||
BlockState state = layers.get(layer);
|
||||
if (state == null) continue;
|
||||
int y = chunk.getMinY() + layer;
|
||||
for (int x = 0; x < 16; x++) for (int z = 0; z < 16; z++) {
|
||||
chunk.setBlockState(pos.set(x, y, z), state);
|
||||
floor.update(x, y, z, state);
|
||||
surface.update(x, y, z, state);
|
||||
}
|
||||
}
|
||||
return CompletableFuture.completedFuture(chunk);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBaseHeight(int x, int z, Heightmap.Types type,
|
||||
LevelHeightAccessor height, RandomState random) {
|
||||
List<BlockState> layers = settings.getLayers();
|
||||
for (int layer = Math.min(layers.size() - 1, height.getHeight() - 1); layer >= 0; layer--) {
|
||||
BlockState state = layers.get(layer);
|
||||
if (state != null && type.isOpaque().test(state)) return height.getMinY() + layer + 1;
|
||||
}
|
||||
return height.getMinY();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NoiseColumn getBaseColumn(int x, int z, LevelHeightAccessor height, RandomState random) {
|
||||
return new NoiseColumn(height.getMinY(), settings.getLayers().stream().limit(height.getHeight())
|
||||
.map(state -> state == null ? Blocks.AIR.defaultBlockState() : state)
|
||||
.toArray(BlockState[]::new));
|
||||
}
|
||||
|
||||
@Override public void addDebugScreenInfo(List<String> info, RandomState random,
|
||||
BlockPos pos, SamplerContext context) { }
|
||||
@Override public void spawnOriginalMobs(WorldGenRegion level) { }
|
||||
@Override public int getMinY() { return 0; }
|
||||
@Override public int getGenDepth() { return 384; }
|
||||
@Override public int getSeaLevel() { return -63; }
|
||||
|
||||
@Override
|
||||
public void refreshFeaturesPerStep() {
|
||||
super.refreshFeaturesPerStep();
|
||||
generationSettingsGetter.clear();
|
||||
settings.updateLayers();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
@@ -20,12 +21,18 @@ import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.TicketStorage;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.biome.BiomeSource;
|
||||
import net.minecraft.world.level.biome.FixedBiomeSource;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.Biomes;
|
||||
import net.minecraft.world.level.biome.CheckerboardColumnBiomeSource;
|
||||
import net.minecraft.world.level.dimension.LevelStem;
|
||||
import net.minecraft.world.level.levelgen.FlatLevelSource;
|
||||
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
|
||||
import net.minecraft.world.level.levelgen.NoiseGeneratorSettings;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
|
||||
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.storage.DerivedLevelData;
|
||||
import net.minecraft.world.level.storage.LevelResource;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
@@ -254,6 +261,25 @@ public final class RuntimeAgeBackend {
|
||||
private record ArchivePaths(Path live, Path archive) {
|
||||
}
|
||||
|
||||
private static BiomeSource authoredBiomeSource(MinecraftServer server, PortBiomePlan plan, long seed) {
|
||||
if (plan.distribution() == PortBiomePlan.Distribution.NATIVE)
|
||||
throw new IllegalArgumentException("Native biome distribution has no authored source");
|
||||
var registry = server.registryAccess().lookupOrThrow(Registries.BIOME);
|
||||
List<Holder<Biome>> biomes = plan.biomes().stream().map(id ->
|
||||
(Holder<Biome>) registry.get(ResourceKey.create(Registries.BIOME, id))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unknown authored biome: " + id)))
|
||||
.toList();
|
||||
return switch (plan.distribution()) {
|
||||
case NATIVE -> throw new IllegalStateException("Native is not an override");
|
||||
case SINGLE -> new FixedBiomeSource(biomes.getFirst());
|
||||
case TILED, GRID -> new CheckerboardColumnBiomeSource(HolderSet.direct(biomes),
|
||||
plan.distribution() == PortBiomePlan.Distribution.GRID ? 0 : 4);
|
||||
case TINY, SMALL, MEDIUM, LARGE, HUGE -> new PortScaledBiomeSource(
|
||||
HolderSet.direct(biomes), seed, plan.distribution().ordinal() -
|
||||
PortBiomePlan.Distribution.TINY.ordinal());
|
||||
};
|
||||
}
|
||||
|
||||
private static ServerLevel load(MinecraftServer server, Identifier id, long seed,
|
||||
PortBiomePlan biomePlan, PortAgeTerrain terrain) {
|
||||
ResourceKey<Level> key = ResourceKey.create(Registries.DIMENSION, id);
|
||||
@@ -266,26 +292,42 @@ public final class RuntimeAgeBackend {
|
||||
.getValue(LevelStem.OVERWORLD);
|
||||
if (overworldStem == null) throw new IllegalStateException("Missing Overworld LevelStem");
|
||||
var generator = overworldStem.generator();
|
||||
if ((biomePlan != null && biomePlan.distribution() != PortBiomePlan.Distribution.NATIVE)
|
||||
if (terrain == PortAgeTerrain.FLAT || terrain == PortAgeTerrain.VOID) {
|
||||
var biomes = server.registryAccess().lookupOrThrow(Registries.BIOME);
|
||||
Holder<Biome> biome = biomes.getOrThrow(terrain == PortAgeTerrain.VOID
|
||||
? Biomes.THE_VOID : Biomes.PLAINS);
|
||||
if (biomePlan != null && !biomePlan.biomes().isEmpty()) {
|
||||
Identifier biomeId = biomePlan.biomes().getFirst();
|
||||
biome = biomes.get(ResourceKey.create(Registries.BIOME, biomeId))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unknown authored biome: " + biomeId));
|
||||
}
|
||||
FlatLevelGeneratorSettings settings;
|
||||
if (terrain == PortAgeTerrain.FLAT) {
|
||||
settings = FlatLevelGeneratorSettings.getDefault(biomes,
|
||||
server.registryAccess().lookupOrThrow(Registries.STRUCTURE_SET),
|
||||
server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE));
|
||||
// Vanilla superflat begins at the modern minimum build height.
|
||||
// Pad the column with air so the first solid layer begins at Y=0.
|
||||
int airBelowZero = Math.max(0, -server.overworld().getMinY());
|
||||
if (airBelowZero > 0)
|
||||
settings.getLayersInfo().addFirst(new FlatLayerInfo(airBelowZero, Blocks.AIR));
|
||||
settings = settings.withBiomeAndLayers(settings.getLayersInfo(),
|
||||
settings.structureOverrides(), biome);
|
||||
settings.setDecoration();
|
||||
} else {
|
||||
settings = new FlatLevelGeneratorSettings(Optional.empty(), biome, List.of());
|
||||
settings.updateLayers();
|
||||
}
|
||||
generator = biomePlan != null && biomePlan.biomes().size() > 1
|
||||
? new PortFlatChunkGenerator(authoredBiomeSource(server, biomePlan, seed), settings)
|
||||
: new FlatLevelSource(settings);
|
||||
} else if ((biomePlan != null && biomePlan.distribution() != PortBiomePlan.Distribution.NATIVE)
|
||||
|| terrain != PortAgeTerrain.STANDARD) {
|
||||
if (!(generator instanceof NoiseBasedChunkGenerator noise))
|
||||
throw new IllegalStateException("Overworld generator cannot accept authored noise settings");
|
||||
var source = generator.getBiomeSource();
|
||||
if (biomePlan != null && biomePlan.distribution() != PortBiomePlan.Distribution.NATIVE) {
|
||||
var registry = server.registryAccess().lookupOrThrow(Registries.BIOME);
|
||||
List<Holder<Biome>> biomes = biomePlan.biomes().stream().map(biome ->
|
||||
(Holder<Biome>) registry.get(ResourceKey.create(Registries.BIOME, biome))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unknown authored biome: " + biome)))
|
||||
.toList();
|
||||
source = switch (biomePlan.distribution()) {
|
||||
case NATIVE -> throw new IllegalStateException("Native is not an override");
|
||||
case SINGLE -> new FixedBiomeSource(biomes.getFirst());
|
||||
case TILED, GRID -> new CheckerboardColumnBiomeSource(HolderSet.direct(biomes),
|
||||
biomePlan.distribution() == PortBiomePlan.Distribution.GRID ? 0 : 4);
|
||||
case TINY, SMALL, MEDIUM, LARGE, HUGE -> new PortScaledBiomeSource(
|
||||
HolderSet.direct(biomes), seed, biomePlan.distribution().ordinal() -
|
||||
PortBiomePlan.Distribution.TINY.ordinal());
|
||||
};
|
||||
source = authoredBiomeSource(server, biomePlan, seed);
|
||||
}
|
||||
var settings = switch (terrain) {
|
||||
case STANDARD -> noise.generatorSettings();
|
||||
@@ -293,6 +335,7 @@ public final class RuntimeAgeBackend {
|
||||
.getOrThrow(NoiseGeneratorSettings.AMPLIFIED);
|
||||
case ISLAND -> server.registryAccess().lookupOrThrow(Registries.NOISE_SETTINGS)
|
||||
.getOrThrow(NoiseGeneratorSettings.FLOATING_ISLANDS);
|
||||
case FLAT, VOID -> throw new IllegalStateException("Flat terrain should use FlatLevelSource");
|
||||
};
|
||||
generator = new NoiseBasedChunkGenerator(source, settings);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package net.dnorberg.rivencraft.registry;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.age.PortFlatChunkGenerator;
|
||||
import net.minecraft.core.registries.Registries;
|
||||
import net.minecraft.world.level.chunk.ChunkGenerator;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.neoforge.registries.DeferredRegister;
|
||||
|
||||
/** Codec for the authored Flat/Void biome source and layer combination. */
|
||||
public final class PortChunkGenerators {
|
||||
private static final DeferredRegister<MapCodec<? extends ChunkGenerator>> GENERATORS =
|
||||
DeferredRegister.create(Registries.CHUNK_GENERATOR, Rivencraft.MOD_ID);
|
||||
|
||||
static {
|
||||
GENERATORS.register("age_flat", () -> PortFlatChunkGenerator.CODEC);
|
||||
}
|
||||
|
||||
private PortChunkGenerators() {}
|
||||
|
||||
public static void register(IEventBus bus) {
|
||||
GENERATORS.register(bus);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,10 @@ public final class PortAuthoredWriting {
|
||||
Rivencraft.MOD_ID, "terrain/amplified");
|
||||
private static final Identifier ISLAND_TERRAIN = Identifier.fromNamespaceAndPath(
|
||||
Rivencraft.MOD_ID, "terrain/island");
|
||||
private static final Identifier FLAT_TERRAIN = Identifier.fromNamespaceAndPath(
|
||||
Rivencraft.MOD_ID, "terrain/flat");
|
||||
private static final Identifier VOID_TERRAIN = Identifier.fromNamespaceAndPath(
|
||||
Rivencraft.MOD_ID, "terrain/void");
|
||||
|
||||
private PortAuthoredWriting() {}
|
||||
|
||||
@@ -28,8 +32,7 @@ public final class PortAuthoredWriting {
|
||||
int biomes = 0;
|
||||
int controllers = 0;
|
||||
for (Identifier page : pages) {
|
||||
if ((page.equals(STANDARD_TERRAIN) || page.equals(AMPLIFIED_TERRAIN)
|
||||
|| page.equals(ISLAND_TERRAIN)) && !terrain) {
|
||||
if (isTerrain(page) && !terrain) {
|
||||
terrain = true;
|
||||
} else if (controller(page) != null && controllers++ == 0) {
|
||||
// An explicit controller needs an authored pool.
|
||||
@@ -44,15 +47,22 @@ public final class PortAuthoredWriting {
|
||||
if (controllers != 0) {
|
||||
PortBiomePlan.Distribution distribution = pages.stream().map(PortAuthoredWriting::controller)
|
||||
.filter(value -> value != null).findFirst().orElseThrow();
|
||||
return switch (distribution) {
|
||||
boolean valid = switch (distribution) {
|
||||
case NATIVE -> biomes == 0;
|
||||
case SINGLE -> biomes == 1;
|
||||
default -> biomes >= 2;
|
||||
};
|
||||
return valid;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private static PortBiomePlan.Distribution controller(Identifier page) {
|
||||
if (!page.getNamespace().equals(Rivencraft.MOD_ID)) return null;
|
||||
return switch (page.getPath()) {
|
||||
@@ -73,18 +83,19 @@ public final class PortAuthoredWriting {
|
||||
if (!supports(pages)) throw new IllegalArgumentException("Unsupported authored Age pages");
|
||||
if (pages.contains(AMPLIFIED_TERRAIN)) return PortAgeTerrain.AMPLIFIED;
|
||||
if (pages.contains(ISLAND_TERRAIN)) return PortAgeTerrain.ISLAND;
|
||||
if (pages.contains(FLAT_TERRAIN)) return PortAgeTerrain.FLAT;
|
||||
if (pages.contains(VOID_TERRAIN)) return PortAgeTerrain.VOID;
|
||||
return PortAgeTerrain.STANDARD;
|
||||
}
|
||||
|
||||
/** Resolves all authored biomes from the active registry before creating a level. */
|
||||
public static PortBiomePlan biomePlan(List<Identifier> pages, HolderLookup.Provider registries) {
|
||||
if (!supports(pages)) throw new IllegalArgumentException(
|
||||
"Only Standard/Amplified/Island terrain with supported Biome pages is available in this port slice");
|
||||
"Unsupported terrain or biome-page combination in this port slice");
|
||||
List<Identifier> biomes = new ArrayList<>();
|
||||
PortBiomePlan.Distribution distribution = null;
|
||||
for (Identifier page : pages) {
|
||||
if (page.equals(STANDARD_TERRAIN) || page.equals(AMPLIFIED_TERRAIN)
|
||||
|| page.equals(ISLAND_TERRAIN)) continue;
|
||||
if (isTerrain(page)) continue;
|
||||
PortBiomePlan.Distribution selected = controller(page);
|
||||
if (selected != null) { distribution = selected; continue; }
|
||||
Identifier biome = PortRegistryPages.biomeId(page);
|
||||
|
||||
@@ -69,6 +69,10 @@ start_server first
|
||||
send_and_wait 'rivencraft_port compat' 'Registered biomes by namespace:'
|
||||
send_and_wait 'rivencraft_port age create smoke_alpha 123456789' 'Created rivencraft:age/smoke_alpha with seed 123456789'
|
||||
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_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'
|
||||
# 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"
|
||||
@@ -78,26 +82,66 @@ index_file="$server_dir/world/dimensions/minecraft/overworld/data/rivencraft/run
|
||||
}
|
||||
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_flat_multi'
|
||||
gzip -dc "$index_file" | strings | grep -Fq 'rivencraft:age/smoke_void_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_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 '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_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'
|
||||
send_and_wait 'execute in rivencraft:age/smoke_flat_multi if block 0 0 0 minecraft:bedrock run say Multi Flat bedrock starts at Y0' 'Multi Flat bedrock starts at Y0'
|
||||
send_and_wait 'execute in rivencraft:age/smoke_void_multi if block 0 0 0 minecraft:air run say Multi Void remains empty' 'Multi Void remains empty'
|
||||
# Check region interiors after loading the sampled chunks.
|
||||
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'
|
||||
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=//')
|
||||
void_before=$(grep -F 'rivencraft:age/smoke_void seed=13579 chunkOffset=0 terrain=' "$log_file" | tail -1 | sed 's/.*terrain=//')
|
||||
if [[ "$alpha_before" == "$beta_before" ]]; then
|
||||
printf 'Different Age seeds produced the same terrain probe: %s\n' "$alpha_before" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$flat_before" == "$void_before" ]]; then
|
||||
printf 'Flat and Void Ages produced the same height probe: %s\n' "$flat_before" >&2
|
||||
exit 1
|
||||
fi
|
||||
stop_server
|
||||
|
||||
start_server restart
|
||||
send_and_wait 'rivencraft_port age list' 'smoke_alpha'
|
||||
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_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 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'
|
||||
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=//')
|
||||
void_after=$(grep -F 'rivencraft:age/smoke_void seed=13579 chunkOffset=0 terrain=' "$log_file" | tail -1 | sed 's/.*terrain=//')
|
||||
[[ "$alpha_before" == "$alpha_after" && "$beta_before" == "$beta_after" ]] || {
|
||||
printf 'Age terrain changed across restart: %s/%s -> %s/%s\n' \
|
||||
"$alpha_before" "$beta_before" "$alpha_after" "$beta_after" >&2
|
||||
exit 1
|
||||
}
|
||||
[[ "$flat_before" == "$flat_after" && "$void_before" == "$void_after" ]] || {
|
||||
printf 'Flat/Void terrain changed across restart: %s/%s -> %s/%s\n' \
|
||||
"$flat_before" "$void_before" "$flat_after" "$void_after" >&2
|
||||
exit 1
|
||||
}
|
||||
stop_server
|
||||
printf '26.3 dedicated-server smoke passed: two distinct Ages persisted across restart.\n'
|
||||
printf '26.3 dedicated-server smoke passed: Standard, Flat and Void Ages persisted across restart.\n'
|
||||
Reference in new issue
Block a user