diff --git a/docs/AGE_PROFILES.md b/docs/AGE_PROFILES.md new file mode 100644 index 0000000..cf455be --- /dev/null +++ b/docs/AGE_PROFILES.md @@ -0,0 +1,50 @@ +# Age runtime profiles + +Rivencraft loads Age runtime profiles from server data packs at +`data//rivencraft/age_profiles/.json`. The resulting profile ID +is `:`. Profiles select registered world-generation resources; +they do not contain an Age's seed, name, spawn, or rolled traits. + +## Schema version 1 + +```json +{ + "schema_version": 1, + "terrain": "noise", + "noise_settings": "minecraft:overworld", + "biome_preset": "minecraft:overworld", + "dimension_type": "minecraft:overworld" +} +``` + +`terrain` is `noise`, `flat`, or `void`. Noise profiles require +`noise_settings` and `biome_preset` references. Every profile requires a +`dimension_type` reference. Omitted resource references use the Overworld +entry. Rivencraft validates all referenced entries against the loaded dynamic +registries during data-pack reload. An invalid profile is rejected. + +The built-in profile IDs are `rivencraft:standard`, `amplified`, `island`, +`cave`, `flat`, and `void`. Their JSON files are ordinary built-in data-pack +resources and can be overridden by a higher-priority pack. The example under +`docs/example-age-profile-pack` can be copied into a world's `datapacks` +directory. Valid custom profiles join the stable, sorted pool used when a new +Age rolls its base terrain; the selected ID is then persisted with that Age. + +## Persistence and reload behavior + +An Age permanently stores its selected profile ID, generation version, seed, +and rolled traits. `/reload` replaces the profile definitions but does not +reroll those immutable values. An unloaded Age resolves its stored profile +again when it loads, so intentional profile overrides affect its runtime +resources and newly generated chunks. + +A profile removed after an Age was created resolves to `rivencraft:standard`. +The legacy `rivencraft:random_vanilla` alias also resolves to `standard`. +Rivencraft logs this fallback. Invalid built-in overrides do not replace the +required built-in definitions; a reload fails if a required built-in profile +is absent after resource resolution. + +Only schema version 1 is accepted. A future schema change must add an explicit +migration path before incrementing the version. The saved Age generation +version remains independent of this data-pack schema and is never silently +rewritten. diff --git a/docs/PORTING.md b/docs/PORTING.md index 2cbeaac..97ab773 100644 --- a/docs/PORTING.md +++ b/docs/PORTING.md @@ -47,7 +47,8 @@ Age references use UUIDs rather than dimension keys. The server-owned Age registry maps each stable identity to its immutable definition and runtime dimension, keeping saved books valid if an Age is migrated or regenerated. Operational lifecycle and unload behavior are documented in -[AGES.md](AGES.md). +[AGES.md](AGES.md). The server data-pack schema for runtime generation is +documented in [AGE_PROFILES.md](AGE_PROFILES.md). The core mod must remain usable without Create, Ponder, or Immersive Portals. The built-in legacy animation is the dependency-free destination preview. diff --git a/docs/example-age-profile-pack/data/example/rivencraft/age_profiles/highlands.json b/docs/example-age-profile-pack/data/example/rivencraft/age_profiles/highlands.json new file mode 100644 index 0000000..55bea2e --- /dev/null +++ b/docs/example-age-profile-pack/data/example/rivencraft/age_profiles/highlands.json @@ -0,0 +1,7 @@ +{ + "schema_version": 1, + "terrain": "noise", + "noise_settings": "minecraft:amplified", + "biome_preset": "minecraft:overworld", + "dimension_type": "minecraft:overworld" +} diff --git a/docs/example-age-profile-pack/pack.mcmeta b/docs/example-age-profile-pack/pack.mcmeta new file mode 100644 index 0000000..2f80346 --- /dev/null +++ b/docs/example-age-profile-pack/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "pack_format": 48, + "description": "Example Rivencraft Age runtime profile" + } +} diff --git a/gradle.properties b/gradle.properties index 055c082..f02fde9 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.27.0 +mod_version=0.2.28.0 immersive_portals_version=6.0.7 # 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. diff --git a/src/main/java/net/dnorberg/rivencraft/Rivencraft.java b/src/main/java/net/dnorberg/rivencraft/Rivencraft.java index f50abbd..2363172 100644 --- a/src/main/java/net/dnorberg/rivencraft/Rivencraft.java +++ b/src/main/java/net/dnorberg/rivencraft/Rivencraft.java @@ -17,6 +17,7 @@ import net.dnorberg.rivencraft.config.RivencraftConfig; import net.dnorberg.rivencraft.config.RivencraftClientConfig; import net.dnorberg.rivencraft.book.AgeDimensionService; import net.dnorberg.rivencraft.book.AgeWeatherService; +import net.dnorberg.rivencraft.book.AgeRuntimeProfiles; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModContainer; import net.neoforged.fml.common.Mod; @@ -48,6 +49,7 @@ public final class Rivencraft { NeoForge.EVENT_BUS.addListener(AgeLifecycleEvents::onPlayerLoggedOut); NeoForge.EVENT_BUS.addListener(AgeLifecycleEvents::onServerStopping); NeoForge.EVENT_BUS.addListener(RivencraftCommands::register); + NeoForge.EVENT_BUS.addListener(AgeRuntimeProfiles::register); LOGGER.info("Initializing Rivencraft"); } } diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java b/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java index 960e0c3..e8f06ff 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java @@ -5,7 +5,6 @@ import dev.galacticraft.dynamicdimensions.api.PlayerRemover; import dev.galacticraft.dynamicdimensions.api.event.DimensionRemovedCallback; import dev.galacticraft.dynamicdimensions.api.event.DynamicDimensionLoadCallback; import java.util.Optional; -import java.util.OptionalLong; import java.util.List; import java.util.Comparator; import java.util.Collections; @@ -23,7 +22,6 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.tags.BlockTags; -import net.minecraft.util.valueproviders.UniformInt; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.biome.Biomes; @@ -32,7 +30,6 @@ import net.minecraft.world.level.biome.BiomeSource; import net.minecraft.world.level.biome.CheckerboardColumnBiomeSource; import net.minecraft.world.level.biome.FixedBiomeSource; import net.minecraft.world.level.biome.MultiNoiseBiomeSource; -import net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterLists; import net.dnorberg.rivencraft.world.ScaledBiomeSource; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.dimension.BuiltinDimensionTypes; @@ -65,7 +62,7 @@ public final class AgeDimensionService { ServerLevel level = loader.loadDynamicDimension( age.dimensionId(), createGenerator(server, age), - createDimensionType(age) + createDimensionType(server, age) ); if (level != null) { applyCelestialClock(level, age); @@ -107,7 +104,7 @@ public final class AgeDimensionService { ServerLevel loaded = DynamicDimensionRegistry.from(server).loadDynamicDimension( definition.dimensionId(), createGenerator(server, definition), - createDimensionType(definition) + createDimensionType(server, definition) ); if (loaded == null) { Rivencraft.LOGGER.error("Failed to load Age {} as {}", definition.id(), definition.dimensionId()); @@ -264,15 +261,15 @@ public final class AgeDimensionService { } private static ChunkGenerator createGenerator(MinecraftServer server, AgeDefinition age) { - ResourceLocation profile = age.generationProfile(); - if (profile.equals(AgeGenerationProfiles.FLAT)) { + AgeRuntimeProfile profile = AgeRuntimeProfiles.resolve(age.generationProfile()); + if (profile.terrain() == AgeRuntimeProfile.Terrain.FLAT) { return new FlatLevelSource(FlatLevelGeneratorSettings.getDefault( server.registryAccess().lookupOrThrow(Registries.BIOME), server.registryAccess().lookupOrThrow(Registries.STRUCTURE_SET), server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE) )); } - if (profile.equals(AgeGenerationProfiles.VOID)) { + if (profile.terrain() == AgeRuntimeProfile.Terrain.VOID) { FlatLevelGeneratorSettings settings = new FlatLevelGeneratorSettings( Optional.empty(), server.registryAccess().lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID), @@ -285,22 +282,11 @@ public final class AgeDimensionService { var biomeParameters = server.registryAccess() .lookupOrThrow(Registries.MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST); var noiseSettings = server.registryAccess().lookupOrThrow(Registries.NOISE_SETTINGS); - ResourceKey settingsKey; - if (profile.equals(AgeGenerationProfiles.CAVE)) { - settingsKey = NoiseGeneratorSettings.CAVES; - } else if (profile.equals(AgeGenerationProfiles.ISLAND)) { - settingsKey = NoiseGeneratorSettings.FLOATING_ISLANDS; - } else if (profile.equals(AgeGenerationProfiles.AMPLIFIED)) { - settingsKey = NoiseGeneratorSettings.AMPLIFIED; - } else { - settingsKey = NoiseGeneratorSettings.OVERWORLD; - } - // Cave uses Minecraft's enclosed cave density with stone, water, and - // Overworld-like surfaces while retaining the Age's biome source. - // Island/Skylands uses uniform floating-island density without the - // End's central dragon island. Both use the Overworld environment. - // random_vanilla is retained as a migration alias for existing Ages. - BiomeSource biomeSource = createBiomeSource(server, age, biomeParameters); + ResourceKey settingsKey = ResourceKey.create( + Registries.NOISE_SETTINGS, profile.noiseSettings()); + ResourceKey biomePreset = + ResourceKey.create(Registries.MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST, profile.biomePreset()); + BiomeSource biomeSource = createBiomeSource(server, age, biomeParameters, biomePreset); NoiseGeneratorSettings baseSettings = noiseSettings.getOrThrow(settingsKey).value(); return new NoiseBasedChunkGenerator(biomeSource, Holder.direct(withBiomeMaterialRules(baseSettings))); } @@ -347,7 +333,8 @@ public final class AgeDimensionService { private static BiomeSource createBiomeSource( MinecraftServer server, AgeDefinition age, - net.minecraft.core.HolderGetter biomeParameters + net.minecraft.core.HolderGetter biomeParameters, + ResourceKey biomePreset ) { List> biomes = server.registryAccess() .lookupOrThrow(Registries.BIOME) @@ -394,29 +381,32 @@ public final class AgeDimensionService { ); } return MultiNoiseBiomeSource.createFromPreset( - biomeParameters.getOrThrow(MultiNoiseBiomeSourceParameterLists.OVERWORLD)); + biomeParameters.getOrThrow(biomePreset)); } - private static DimensionType createDimensionType(AgeDefinition age) { + private static DimensionType createDimensionType(MinecraftServer server, AgeDefinition age) { + AgeRuntimeProfile profile = AgeRuntimeProfiles.resolve(age.generationProfile()); + DimensionType base = server.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE) + .getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, profile.dimensionType())).value(); AgeTraits.Lighting lighting = age.traits().lighting(); boolean hasSkyLight = lighting != AgeTraits.Lighting.DARK; float ambientLight = lighting == AgeTraits.Lighting.BRIGHT ? 0.25F : 0.0F; return new DimensionType( - OptionalLong.empty(), + base.fixedTime(), hasSkyLight, - false, - false, - true, - 1.0D, - false, - false, - -64, - 384, - 384, - BlockTags.INFINIBURN_OVERWORLD, - BuiltinDimensionTypes.OVERWORLD_EFFECTS, + base.hasCeiling(), + base.ultraWarm(), + base.natural(), + base.coordinateScale(), + base.bedWorks(), + base.respawnAnchorWorks(), + base.minY(), + base.height(), + base.logicalHeight(), + base.infiniburn(), + base.effectsLocation(), ambientLight, - new DimensionType.MonsterSettings(false, true, UniformInt.of(0, 7), 0) + base.monsterSettings() ); } diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeGenerationProfiles.java b/src/main/java/net/dnorberg/rivencraft/book/AgeGenerationProfiles.java index 1ff3c20..2f456a0 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeGenerationProfiles.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeGenerationProfiles.java @@ -33,6 +33,14 @@ public final class AgeGenerationProfiles { return RANDOM_PROFILES; } + /** Maps removed legacy aliases to a stable built-in profile. */ + public static ResourceLocation migrationFallback(ResourceLocation profile) { + if (profile.equals(AgeDefinition.RANDOM_VANILLA_PROFILE)) { + return STANDARD; + } + return isSupported(profile) ? profile : STANDARD; + } + private static ResourceLocation id(String path) { return ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, path); } diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeRegistrySavedData.java b/src/main/java/net/dnorberg/rivencraft/book/AgeRegistrySavedData.java index 47be6a7..1d32812 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeRegistrySavedData.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeRegistrySavedData.java @@ -91,7 +91,7 @@ public final class AgeRegistrySavedData extends SavedData { reference.id(), name, seed, - AgeGenerationProfiles.select(seed), + AgeRuntimeProfiles.select(seed), AgeDefinition.CURRENT_GENERATION_VERSION, createdGameTime, dimensionId, diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeRuntimeProfile.java b/src/main/java/net/dnorberg/rivencraft/book/AgeRuntimeProfile.java new file mode 100644 index 0000000..607852a --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeRuntimeProfile.java @@ -0,0 +1,20 @@ +package net.dnorberg.rivencraft.book; + +import net.minecraft.resources.ResourceLocation; + +/** Data-pack-defined resources used to construct an Age runtime dimension. */ +public record AgeRuntimeProfile( + int schemaVersion, + Terrain terrain, + ResourceLocation noiseSettings, + ResourceLocation biomePreset, + ResourceLocation dimensionType +) { + public static final int CURRENT_SCHEMA_VERSION = 1; + + public enum Terrain { + NOISE, + FLAT, + VOID + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeRuntimeProfiles.java b/src/main/java/net/dnorberg/rivencraft/book/AgeRuntimeProfiles.java new file mode 100644 index 0000000..d3a4612 --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeRuntimeProfiles.java @@ -0,0 +1,140 @@ +package net.dnorberg.rivencraft.book; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import net.dnorberg.rivencraft.Rivencraft; +import net.minecraft.core.RegistryAccess; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; +import net.minecraft.util.profiling.ProfilerFiller; +import net.minecraft.world.level.biome.MultiNoiseBiomeSourceParameterList; +import net.minecraft.world.level.dimension.DimensionType; +import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; +import net.neoforged.neoforge.event.AddReloadListenerEvent; + +/** Loads {@code data//rivencraft/age_profiles/*.json}. */ +public final class AgeRuntimeProfiles { + private static final Gson GSON = new GsonBuilder().create(); + private static final String DIRECTORY = "rivencraft/age_profiles"; + private static final Map PROFILES = new ConcurrentHashMap<>(); + + private AgeRuntimeProfiles() { + } + + public static void register(AddReloadListenerEvent event) { + event.addListener(new Loader(event.getRegistryAccess())); + } + + public static AgeRuntimeProfile resolve(ResourceLocation id) { + AgeRuntimeProfile profile = PROFILES.get(id); + if (profile != null) { + return profile; + } + ResourceLocation fallback = AgeGenerationProfiles.migrationFallback(id); + profile = PROFILES.get(fallback); + if (profile == null) { + throw new IllegalStateException("Missing built-in Age runtime profile " + fallback); + } + Rivencraft.LOGGER.warn("Age runtime profile {} is unavailable; using deterministic fallback {}", id, fallback); + return profile; + } + + public static Map profiles() { + return Map.copyOf(PROFILES); + } + + /** Selects from all valid loaded profiles in stable resource-ID order. */ + public static ResourceLocation select(long seed) { + if (PROFILES.isEmpty()) { + return AgeGenerationProfiles.select(seed); + } + java.util.List ids = PROFILES.keySet().stream().sorted().toList(); + return ids.get(Math.floorMod(Long.hashCode(seed), ids.size())); + } + + static AgeRuntimeProfile parse(ResourceLocation id, JsonElement json, RegistryAccess registries) { + JsonObject object = json.getAsJsonObject(); + int schemaVersion = object.has("schema_version") ? object.get("schema_version").getAsInt() : 1; + if (schemaVersion != AgeRuntimeProfile.CURRENT_SCHEMA_VERSION) { + throw new IllegalArgumentException("unsupported schema_version " + schemaVersion); + } + AgeRuntimeProfile.Terrain terrain = AgeRuntimeProfile.Terrain.valueOf( + requiredString(object, "terrain").toUpperCase(java.util.Locale.ROOT)); + ResourceLocation noise = location(object, "noise_settings", "minecraft:overworld"); + ResourceLocation biome = location(object, "biome_preset", "minecraft:overworld"); + ResourceLocation dimension = location(object, "dimension_type", "minecraft:overworld"); + + if (terrain == AgeRuntimeProfile.Terrain.NOISE) { + requireRegistryEntry(registries, Registries.NOISE_SETTINGS, noise, id, "noise_settings"); + requireRegistryEntry(registries, Registries.MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST, + biome, id, "biome_preset"); + } + requireRegistryEntry(registries, Registries.DIMENSION_TYPE, dimension, id, "dimension_type"); + return new AgeRuntimeProfile(schemaVersion, terrain, noise, biome, dimension); + } + + private static String requiredString(JsonObject object, String name) { + if (!object.has(name)) { + throw new IllegalArgumentException("missing " + name); + } + return object.get(name).getAsString(); + } + + private static ResourceLocation location(JsonObject object, String name, String fallback) { + ResourceLocation id = ResourceLocation.tryParse(object.has(name) ? object.get(name).getAsString() : fallback); + if (id == null) { + throw new IllegalArgumentException("invalid " + name); + } + return id; + } + + private static void requireRegistryEntry( + RegistryAccess registries, + ResourceKey> registry, + ResourceLocation entry, + ResourceLocation profile, + String field + ) { + if (registries.lookupOrThrow(registry).get(ResourceKey.create(registry, entry)).isEmpty()) { + throw new IllegalArgumentException(profile + " references missing " + field + " " + entry); + } + } + + private static final class Loader extends SimpleJsonResourceReloadListener { + private final RegistryAccess registries; + + private Loader(RegistryAccess registries) { + super(GSON, DIRECTORY); + this.registries = registries; + } + + @Override + protected void apply(Map resources, + ResourceManager resourceManager, + ProfilerFiller profiler) { + Map loaded = new java.util.HashMap<>(); + resources.forEach((id, json) -> { + try { + loaded.put(id, parse(id, json, registries)); + } catch (RuntimeException exception) { + Rivencraft.LOGGER.error("Ignoring invalid Age runtime profile {}: {}", id, exception.getMessage()); + } + }); + for (ResourceLocation required : AgeGenerationProfiles.all()) { + if (!loaded.containsKey(required)) { + throw new IllegalStateException("Required built-in Age runtime profile is missing: " + required); + } + } + PROFILES.clear(); + PROFILES.putAll(loaded); + Rivencraft.LOGGER.info("Loaded {} Age runtime profiles", loaded.size()); + } + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/gametest/LinkingGameTests.java b/src/main/java/net/dnorberg/rivencraft/gametest/LinkingGameTests.java index 43ad1a7..30f4c97 100644 --- a/src/main/java/net/dnorberg/rivencraft/gametest/LinkingGameTests.java +++ b/src/main/java/net/dnorberg/rivencraft/gametest/LinkingGameTests.java @@ -6,6 +6,8 @@ import net.dnorberg.rivencraft.book.AgeDimensionService; import net.dnorberg.rivencraft.book.AgeGenerationProfiles; import net.dnorberg.rivencraft.book.AgeReference; import net.dnorberg.rivencraft.book.AgeRegistrySavedData; +import net.dnorberg.rivencraft.book.AgeRuntimeProfile; +import net.dnorberg.rivencraft.book.AgeRuntimeProfiles; import net.dnorberg.rivencraft.book.LinkTarget; import net.dnorberg.rivencraft.book.LinkingService; import net.dnorberg.rivencraft.book.AgeVisitSavedData; @@ -38,6 +40,44 @@ public final class LinkingGameTests { private LinkingGameTests() { } + @GameTest(template = "empty") + public static void dataPackAgeProfilesResolveRegisteredResources(GameTestHelper helper) { + var registries = helper.getLevel().registryAccess(); + var profiles = AgeRuntimeProfiles.profiles(); + helper.assertTrue(profiles.keySet().containsAll(AgeGenerationProfiles.all()), + "Every built-in Age runtime profile must survive data-pack validation"); + for (var entry : profiles.entrySet()) { + AgeRuntimeProfile profile = entry.getValue(); + helper.assertValueEqual(profile.schemaVersion(), AgeRuntimeProfile.CURRENT_SCHEMA_VERSION, + entry.getKey() + " must use the current runtime-profile schema"); + helper.assertTrue(registries.lookupOrThrow(net.minecraft.core.registries.Registries.DIMENSION_TYPE) + .get(net.minecraft.resources.ResourceKey.create( + net.minecraft.core.registries.Registries.DIMENSION_TYPE, + profile.dimensionType())).isPresent(), + entry.getKey() + " must reference a registered dimension type"); + if (profile.terrain() == AgeRuntimeProfile.Terrain.NOISE) { + helper.assertTrue(registries.lookupOrThrow(net.minecraft.core.registries.Registries.NOISE_SETTINGS) + .get(net.minecraft.resources.ResourceKey.create( + net.minecraft.core.registries.Registries.NOISE_SETTINGS, + profile.noiseSettings())).isPresent(), + entry.getKey() + " must reference registered noise settings"); + helper.assertTrue(registries.lookupOrThrow( + net.minecraft.core.registries.Registries.MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST) + .get(net.minecraft.resources.ResourceKey.create( + net.minecraft.core.registries.Registries.MULTI_NOISE_BIOME_SOURCE_PARAMETER_LIST, + profile.biomePreset())).isPresent(), + entry.getKey() + " must reference a registered biome preset"); + } + } + var missing = net.minecraft.resources.ResourceLocation.fromNamespaceAndPath("example", "removed_profile"); + helper.assertValueEqual( + AgeRuntimeProfiles.resolve(missing), + AgeRuntimeProfiles.resolve(AgeGenerationProfiles.STANDARD), + "A removed custom profile must use the documented deterministic fallback" + ); + helper.succeed(); + } + @GameTest(template = "empty") public static void linkTargetSurvivesItemSerialization(GameTestHelper helper) { LinkTarget expected = new LinkTarget( diff --git a/src/main/resources/data/rivencraft/rivencraft/age_profiles/amplified.json b/src/main/resources/data/rivencraft/rivencraft/age_profiles/amplified.json new file mode 100644 index 0000000..55bea2e --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/age_profiles/amplified.json @@ -0,0 +1,7 @@ +{ + "schema_version": 1, + "terrain": "noise", + "noise_settings": "minecraft:amplified", + "biome_preset": "minecraft:overworld", + "dimension_type": "minecraft:overworld" +} diff --git a/src/main/resources/data/rivencraft/rivencraft/age_profiles/cave.json b/src/main/resources/data/rivencraft/rivencraft/age_profiles/cave.json new file mode 100644 index 0000000..068e1f1 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/age_profiles/cave.json @@ -0,0 +1,7 @@ +{ + "schema_version": 1, + "terrain": "noise", + "noise_settings": "minecraft:caves", + "biome_preset": "minecraft:overworld", + "dimension_type": "minecraft:overworld" +} diff --git a/src/main/resources/data/rivencraft/rivencraft/age_profiles/flat.json b/src/main/resources/data/rivencraft/rivencraft/age_profiles/flat.json new file mode 100644 index 0000000..fe5bcd8 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/age_profiles/flat.json @@ -0,0 +1,5 @@ +{ + "schema_version": 1, + "terrain": "flat", + "dimension_type": "minecraft:overworld" +} diff --git a/src/main/resources/data/rivencraft/rivencraft/age_profiles/island.json b/src/main/resources/data/rivencraft/rivencraft/age_profiles/island.json new file mode 100644 index 0000000..2662fea --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/age_profiles/island.json @@ -0,0 +1,7 @@ +{ + "schema_version": 1, + "terrain": "noise", + "noise_settings": "minecraft:floating_islands", + "biome_preset": "minecraft:overworld", + "dimension_type": "minecraft:overworld" +} diff --git a/src/main/resources/data/rivencraft/rivencraft/age_profiles/standard.json b/src/main/resources/data/rivencraft/rivencraft/age_profiles/standard.json new file mode 100644 index 0000000..ca65337 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/age_profiles/standard.json @@ -0,0 +1,7 @@ +{ + "schema_version": 1, + "terrain": "noise", + "noise_settings": "minecraft:overworld", + "biome_preset": "minecraft:overworld", + "dimension_type": "minecraft:overworld" +} diff --git a/src/main/resources/data/rivencraft/rivencraft/age_profiles/void.json b/src/main/resources/data/rivencraft/rivencraft/age_profiles/void.json new file mode 100644 index 0000000..7e8dea9 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/age_profiles/void.json @@ -0,0 +1,5 @@ +{ + "schema_version": 1, + "terrain": "void", + "dimension_type": "minecraft:overworld" +}