diff --git a/gradle.properties b/gradle.properties index a1b2daa..8c8bc40 100644 --- a/gradle.properties +++ b/gradle.properties @@ -36,7 +36,7 @@ mod_name=Rivencraft # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=LGPL-3.0-or-later # The mod version. Rivencraft uses the x.y.z.b policy documented in README.md. -mod_version=1.0.0.2 +mod_version=1.0.1.1 immersive_portals_version=6.0.7 terrablender_version=4.1.0.8 serene_seasons_version_id=pHEgQQUE diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeBiomeSelection.java b/src/main/java/net/dnorberg/rivencraft/book/AgeBiomeSelection.java index 0ed8b6c..ad053c2 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeBiomeSelection.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeBiomeSelection.java @@ -8,6 +8,7 @@ import java.util.Random; import java.util.Set; import net.dnorberg.rivencraft.Rivencraft; import net.dnorberg.rivencraft.compat.TerraBlenderBiomes; +import net.dnorberg.rivencraft.writing.BiomeSymbolPages; import net.dnorberg.rivencraft.writing.SymbolSemantics; import net.dnorberg.rivencraft.writing.WritingGrammar; import net.minecraft.core.Holder; @@ -34,6 +35,12 @@ public record AgeBiomeSelection(AgeTraits.BiomeDistribution distribution, List authored = writing.symbols().stream() .filter(symbol -> symbol.semantics().role() == SymbolSemantics.Role.BIOME) .map(symbol -> ((SymbolSemantics.ResourceValue) symbol.semantics().value()).id()).toList(); @@ -67,10 +74,26 @@ public record AgeBiomeSelection(AgeTraits.BiomeDistribution distribution, List availableBiomes = eligible(registries, profile); - if (availableBiomes.isEmpty()) throw new IllegalArgumentException("No eligible biomes for terrain " + profile.noiseSettings()); + List terrainBiomes = eligible(registries, profile); + if (terrainBiomes.isEmpty()) throw new IllegalArgumentException("No eligible biomes for terrain " + profile.noiseSettings()); + List availableBiomes = distribution == AgeTraits.BiomeDistribution.NATIVE + ? terrainBiomes : new ArrayList<>(policy.eligible(terrainBiomes, BiomeSymbolPages::symbolId)); + if (distribution != AgeTraits.BiomeDistribution.NATIVE + && new LinkedHashSet<>(availableBiomes).size() < minimum(distribution)) { + ResourceLocation preferred = profile.terrain() == AgeRuntimeProfile.Terrain.VOID + ? ResourceLocation.withDefaultNamespace("the_void") + : cave ? ResourceLocation.withDefaultNamespace("deep_dark") + : ResourceLocation.withDefaultNamespace("plains"); + if (terrainBiomes.contains(preferred) && !availableBiomes.contains(preferred)) + availableBiomes.add(preferred); + for (ResourceLocation fallback : terrainBiomes) { + if (new LinkedHashSet<>(availableBiomes).size() >= minimum(distribution)) break; + if (!availableBiomes.contains(fallback)) availableBiomes.add(fallback); + } + } if (distribution == AgeTraits.BiomeDistribution.NATIVE) { if (!authored.isEmpty()) throw new IllegalArgumentException("Native biome distribution cannot use biome pages"); + // Native noise biomes are supplied by Minecraft's preset, not chosen as Pages. return new AgeBiomeSelection(distribution, profile.terrain() == AgeRuntimeProfile.Terrain.FLAT ? List.of(ResourceLocation.withDefaultNamespace("plains")) : isCave(profile) ? availableBiomes : presetBiomes(registries, profile)); diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeDefinition.java b/src/main/java/net/dnorberg/rivencraft/book/AgeDefinition.java index 7f279f4..ef030b0 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeDefinition.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeDefinition.java @@ -23,7 +23,8 @@ public record AgeDefinition( @Nullable AgeSeasonMode seasonMode, List randomFeatures, AgeInstability instability, - AgeInstabilityPlan instabilityPlan + AgeInstabilityPlan instabilityPlan, + RandomPagePolicy randomPagePolicy ) { public static final ResourceLocation RANDOM_VANILLA_PROFILE = ResourceLocation.fromNamespaceAndPath( Rivencraft.MOD_ID, "random_vanilla" @@ -39,6 +40,7 @@ public record AgeDefinition( Objects.requireNonNull(traits, "traits"); Objects.requireNonNull(instability, "instability"); Objects.requireNonNull(instabilityPlan, "instabilityPlan"); + Objects.requireNonNull(randomPagePolicy, "randomPagePolicy"); randomFeatures = List.copyOf(randomFeatures); if (plan != null && !randomFeatures.isEmpty()) { throw new IllegalArgumentException("Authored Ages cannot also have random features"); @@ -88,6 +90,16 @@ public record AgeDefinition( AgeInstabilityPlan.empty()); } + public AgeDefinition(UUID id, String name, long seed, ResourceLocation generationProfile, + int generationVersion, long createdGameTime, ResourceLocation dimensionId, + BlockPos spawn, AgeTraits traits, AgeGenerationPlan plan, + @Nullable AgeSeasonMode seasonMode, List randomFeatures, + AgeInstability instability, AgeInstabilityPlan instabilityPlan) { + this(id, name, seed, generationProfile, generationVersion, createdGameTime, + dimensionId, spawn, traits, plan, seasonMode, randomFeatures, instability, + instabilityPlan, RandomPagePolicy.UNRESTRICTED); + } + public static ResourceLocation dimensionId(UUID ageId) { return ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "age/" + ageId); } @@ -117,7 +129,7 @@ public record AgeDefinition( public AgeDefinition withSpawn(BlockPos spawn) { return new AgeDefinition(id, name, seed, generationProfile, generationVersion, createdGameTime, dimensionId, spawn.immutable(), traits, plan, seasonMode, - randomFeatures, instability, instabilityPlan); + randomFeatures, instability, instabilityPlan, randomPagePolicy); } /** A frozen authored profile takes precedence over the profile ID on old books. */ diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java b/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java index 77ddc66..1029813 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java @@ -465,7 +465,7 @@ public final class AgeDimensionService { AgeBiomeSelection selection = age.plan() != null && age.plan().biomeSelection() != null ? age.plan().biomeSelection() : AgeBiomeSelection.compile(net.dnorberg.rivencraft.writing.WritingGrammar.parse(OrderedWriting.EMPTY), - age.traits(), profile, age.seed(), server.registryAccess()); + age.traits(), profile, age.seed(), server.registryAccess(), age.randomPagePolicy()); var registry = server.registryAccess().lookupOrThrow(Registries.BIOME); List> biomes = new ArrayList<>(); List fallbacks = AgeBiomeSelection.eligible(server.registryAccess(), profile); diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeFeatureSelection.java b/src/main/java/net/dnorberg/rivencraft/book/AgeFeatureSelection.java index 3cf892b..d773c1d 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeFeatureSelection.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeFeatureSelection.java @@ -158,12 +158,19 @@ public final class AgeFeatureSelection { /** Page-free Ages roll up to two material features, plus rare Skylands, once at creation. */ public static List randomChoices(long ageSeed, AgeRuntimeProfile profile, RegistryAccess registries) { + return randomChoices(ageSeed, profile, registries, RandomPagePolicy.UNRESTRICTED); + } + + public static List randomChoices(long ageSeed, AgeRuntimeProfile profile, RegistryAccess registries, + RandomPagePolicy policy) { Random random = new Random(ageSeed ^ 0x4D4154455249414CL); int count = random.nextInt(3); List> features = new ArrayList<>(); - for (ResourceLocation id : BLOCK_FEATURES) if (error(id, profile, registries) == null) + for (ResourceLocation id : BLOCK_FEATURES) if (error(id, profile, registries) == null + && policy.allows(featurePage(id))) features.add(new LegacyRankChoice.Option<>(id, randomMaterialRank(id))); - for (ResourceLocation id : FLUID_FEATURES) if (error(id, profile, registries) == null) + for (ResourceLocation id : FLUID_FEATURES) if (error(id, profile, registries) == null + && policy.allows(featurePage(id))) features.add(new LegacyRankChoice.Option<>(id, randomMaterialRank(id))); List choices = new ArrayList<>(); while (choices.size() < count && !features.isEmpty()) { @@ -172,23 +179,30 @@ public final class AgeFeatureSelection { var category = solidFeature(feature) ? BlockSymbolPages.CATEGORY : BlockSymbolPages.FLUID_CATEGORY; List materials = BlockSymbolPages.availableBlocks(category).stream() + .filter(id -> policy.allows(BlockSymbolPages.symbolId(id))) .filter(id -> materialError(new Choice(feature, Optional.of(id))) == null).toList(); if (!materials.isEmpty()) choices.add(new Choice(feature, Optional.of(materials.get(random.nextInt(materials.size()))))); } if (profile.terrain() == AgeRuntimeProfile.Terrain.NOISE + && policy.allows(featurePage(id("skylands"))) && Math.floorMod(mix(ageSeed ^ RANDOM_SKYLANDS_SALT), 8) == 0) choices.add(new Choice(id("skylands"), Optional.empty())); - if (error(CRYSTAL_FORMATIONS, profile, registries) == null && random.nextInt(10) == 0) + if (error(CRYSTAL_FORMATIONS, profile, registries) == null + && policy.allows(featurePage(CRYSTAL_FORMATIONS)) && random.nextInt(10) == 0) choices.add(new Choice(CRYSTAL_FORMATIONS, Optional.empty())); - if (randomStarFissure(ageSeed)) + if (policy.allows(featurePage(STAR_FISSURES)) && randomStarFissure(ageSeed)) choices.add(new Choice(STAR_FISSURES, Optional.empty())); AgeBiomeSelection biomes = AgeBiomeSelection.compile(WritingGrammar.parse(OrderedWriting.EMPTY), - AgeTraits.select(ageSeed), profile, ageSeed, registries); - choices.addAll(AgeStructureSelection.randomChoices(ageSeed, profile, biomes, registries)); + AgeTraits.select(ageSeed, policy), profile, ageSeed, registries, policy); + choices.addAll(AgeStructureSelection.randomChoices(ageSeed, profile, biomes, registries, policy)); return List.copyOf(choices); } + private static ResourceLocation featurePage(ResourceLocation id) { + return ResourceLocation.fromNamespaceAndPath("rivencraft", "feature/" + id.getPath()); + } + static int randomMaterialRank(ResourceLocation feature) { if (feature.equals(id("surface_lakes")) || feature.equals(id("deep_lakes"))) return 1; if (feature.equals(id("lava_lakes"))) return 2; // New separate fluid choice. diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeInstability.java b/src/main/java/net/dnorberg/rivencraft/book/AgeInstability.java index c4874f4..09b88d9 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeInstability.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeInstability.java @@ -56,6 +56,11 @@ public record AgeInstability(int version, int score, List breakdow } static AgeInstability compute(AgeGenerationPlan plan, AgeTraits traits, RegistryAccess registries, long ageSeed) { + return compute(plan, traits, registries, ageSeed, RandomPagePolicy.UNRESTRICTED); + } + + static AgeInstability compute(AgeGenerationPlan plan, AgeTraits traits, RegistryAccess registries, + long ageSeed, RandomPagePolicy policy) { Objects.requireNonNull(traits, "traits"); List contributions = new ArrayList<>(); if (plan == null) { @@ -87,7 +92,7 @@ public record AgeInstability(int version, int score, List breakdow contributions.add(new Contribution("bright_lighting", -1, BRIGHT_LIGHTING_COST, "Seeded bright lighting")); } - AgeSkyProfile sky = plan == null && ageSeed != Long.MIN_VALUE ? AgeSkyProfile.random(ageSeed, traits) + AgeSkyProfile sky = plan == null && ageSeed != Long.MIN_VALUE ? AgeSkyProfile.random(ageSeed, traits, policy) : plan == null ? new AgeSkyProfile(AgeSkyProfile.Style.INHERITED, -1, List.of()) : AgeSkyProfile.compile(plan, traits); int seenSuns = 0; diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeRegistrySavedData.java b/src/main/java/net/dnorberg/rivencraft/book/AgeRegistrySavedData.java index 1c99910..310f16a 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeRegistrySavedData.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeRegistrySavedData.java @@ -39,6 +39,7 @@ public final class AgeRegistrySavedData extends SavedData { private static final String TRAITS_TAG = "Traits"; private static final String PLAN_TAG = "GenerationPlan"; private static final String RANDOM_FEATURES_TAG = "RandomFeatures"; + private static final String RANDOM_PAGE_POLICY_TAG = "RandomPagePolicy"; private static final String SEASON_MODE_TAG = "SeasonMode"; private static final String INSTABILITY_TAG = "Instability"; private static final String INSTABILITY_PLAN_TAG = "InstabilityPlan"; @@ -77,9 +78,19 @@ public final class AgeRegistrySavedData extends SavedData { ServerLevel overworld = level.getServer().overworld(); AgeRegistrySavedData data = get(overworld); long seed = ageSeed(overworld.getSeed(), data.availableDimensionId(normalizedName(name))); - AgeGenerationPlan.compile(writing, seed, + AgeGenerationPlan plan = AgeGenerationPlan.compile(writing, seed, AgeRuntimeProfiles.selectForWriting(seed, writing, overworld.registryAccess()), AgeTraits.select(seed), overworld.registryAccess()); + if (plan == null) { + RandomPagePolicy policy = RandomPagePolicy.configured(); + ResourceLocation profileId = AgeRuntimeProfiles.select(seed, policy); + AgeRuntimeProfile profile = AgeRuntimeProfiles.resolve(profileId); + AgeTraits traits = AgeTraits.select(seed, policy); + AgeBiomeSelection.compile(net.dnorberg.rivencraft.writing.WritingGrammar.parse(OrderedWriting.EMPTY), + traits, profile, seed, overworld.registryAccess(), policy); + AgeSkyProfile.random(seed, traits, policy); + AgeSeasonMode.select(null, seed, ModList.get().isLoaded("sereneseasons"), profile, policy); + } } public static ResourceLocation previewGenerationProfile(ServerLevel level, String name) { @@ -155,7 +166,8 @@ public final class AgeRegistrySavedData extends SavedData { AgeDefinition changed = new AgeDefinition(previous.id(), previous.name(), seed, previous.generationProfile(), previous.generationVersion(), previous.createdGameTime(), previous.dimensionId(), previous.spawn(), previous.traits(), plan, previous.seasonMode(), - previous.randomFeatures(), previous.instability(), previous.instabilityPlan()); + previous.randomFeatures(), previous.instability(), previous.instabilityPlan(), + previous.randomPagePolicy()); if (AgeDimensionService.unload(server, previous) == AgeDimensionService.UnloadResult.FAILED) return false; ages.put(ageId, changed); @@ -173,13 +185,19 @@ public final class AgeRegistrySavedData extends SavedData { String name = normalizedName(reference.name()); ResourceLocation dimensionId = availableDimensionId(name); long seed = ageSeed(worldSeed, dimensionId); + RandomPagePolicy policy = RandomPagePolicy.configured(); ResourceLocation seedProfile = AgeRuntimeProfiles.selectForWriting(seed, writing, registries); AgeTraits traits = AgeTraits.select(seed); AgeGenerationPlan plan = AgeGenerationPlan.compile(writing, seed, seedProfile, traits, registries); + if (plan == null) { + seedProfile = AgeRuntimeProfiles.select(seed, policy); + traits = AgeTraits.select(seed, policy); + } AgeTraits effectiveTraits = plan == null ? AgeEnvironmentCompiler.forTerrain(traits, AgeRuntimeProfiles.resolve(seedProfile)) : AgeEnvironmentCompiler.apply(plan, traits); - AgeInstability instability = AgeInstability.compute(plan, effectiveTraits, registries, seed); + AgeInstability instability = AgeInstability.compute(plan, effectiveTraits, registries, seed, + plan == null ? policy : RandomPagePolicy.UNRESTRICTED); AgeInstabilityPlan instabilityPlan = AgeInstabilityPlan.select(instability, seed); AgeDefinition created = new AgeDefinition( reference.id(), @@ -193,11 +211,13 @@ public final class AgeRegistrySavedData extends SavedData { effectiveTraits, plan, AgeSeasonMode.select(plan, seed, ModList.get().isLoaded("sereneseasons"), - plan == null ? AgeRuntimeProfiles.resolve(seedProfile) : plan.runtimeProfile()), + plan == null ? AgeRuntimeProfiles.resolve(seedProfile) : plan.runtimeProfile(), + plan == null ? policy : RandomPagePolicy.UNRESTRICTED), plan == null ? AgeFeatureSelection.randomChoices(seed, - AgeRuntimeProfiles.resolve(seedProfile), registries) : List.of(), + AgeRuntimeProfiles.resolve(seedProfile), registries, policy) : List.of(), instability, - instabilityPlan + instabilityPlan, + plan == null ? policy : RandomPagePolicy.UNRESTRICTED ); ages.put(created.id(), created); setDirty(); @@ -264,7 +284,10 @@ public final class AgeRegistrySavedData extends SavedData { ? AgeSeasonMode.parseOrNull(entry.getString(SEASON_MODE_TAG)) : null, readRandomFeatures(entry), instability, - instabilityPlan + instabilityPlan, + entry.contains(RANDOM_PAGE_POLICY_TAG, CompoundTag.TAG_COMPOUND) + ? RandomPagePolicy.load(entry.getCompound(RANDOM_PAGE_POLICY_TAG)) + : RandomPagePolicy.UNRESTRICTED )); } return data; @@ -309,6 +332,7 @@ public final class AgeRegistrySavedData extends SavedData { entry.put(RANDOM_FEATURES_TAG, featureTags); } if (age.seasonMode() != null) entry.putString(SEASON_MODE_TAG, age.seasonMode().name()); + if (age.plan() == null) entry.put(RANDOM_PAGE_POLICY_TAG, age.randomPagePolicy().save()); entry.put(INSTABILITY_TAG, age.instability().save()); entry.put(INSTABILITY_PLAN_TAG, age.instabilityPlan().save()); serializedAges.add(entry); diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeRuntimeProfiles.java b/src/main/java/net/dnorberg/rivencraft/book/AgeRuntimeProfiles.java index decf341..ac98875 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeRuntimeProfiles.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeRuntimeProfiles.java @@ -57,20 +57,39 @@ public final class AgeRuntimeProfiles { /** Selects from all valid loaded profiles in stable resource-ID order. */ public static ResourceLocation select(long seed) { + return select(seed, RandomPagePolicy.UNRESTRICTED); + } + + public static ResourceLocation select(long seed, RandomPagePolicy policy) { if (PROFILES.isEmpty()) { - return AgeGenerationProfiles.select(seed); + List> profiles = policy.required( + AgeGenerationProfiles.all().stream().sorted() + .map(id -> new LegacyRankChoice.Option<>(id, AgeGenerationProfiles.randomRank(id))) + .toList(), option -> terrainPage(option.value()), + new LegacyRankChoice.Option<>(AgeGenerationProfiles.STANDARD, 1)); + return LegacyRankChoice.choose(new Random(seed ^ 0x5445525241494E4CL), profiles); } - List> profiles = PROFILES.keySet().stream().sorted() + List> available = PROFILES.keySet().stream().sorted() .map(id -> new LegacyRankChoice.Option<>(id, AgeGenerationProfiles.randomRank(id))).toList(); + LegacyRankChoice.Option fallback = available.stream() + .filter(option -> option.value().equals(AgeGenerationProfiles.STANDARD)) + .findFirst().orElse(available.getFirst()); + List> profiles = policy.required(available, + option -> terrainPage(option.value()), fallback); return LegacyRankChoice.choose(new Random(seed ^ 0x5445525241494E4CL), profiles); } + private static ResourceLocation terrainPage(ResourceLocation profile) { + return ResourceLocation.fromNamespaceAndPath("rivencraft", "terrain/" + profile.getPath()); + } + /** Keep seed-selected terrain random, but only among profiles able to host authored features. */ public static ResourceLocation selectForWriting(long seed, OrderedWriting writing, RegistryAccess registries) { - ResourceLocation selected = select(seed); WritingGrammar.Result parsed = WritingGrammar.parse(writing); - if (parsed.activeSymbols().stream().anyMatch(symbol -> - symbol.semantics().role() == SymbolSemantics.Role.TERRAIN)) return selected; + boolean authoredTerrain = parsed.activeSymbols().stream().anyMatch(symbol -> + symbol.semantics().role() == SymbolSemantics.Role.TERRAIN); + ResourceLocation selected = select(seed); + if (authoredTerrain) return selected; List features = parsed.activeSymbols().stream() .filter(symbol -> symbol.semantics().role() == SymbolSemantics.Role.FEATURE) .map(symbol -> ((SymbolSemantics.ResourceValue) symbol.semantics().value()).id()).toList(); diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeSeasonMode.java b/src/main/java/net/dnorberg/rivencraft/book/AgeSeasonMode.java index 5fee177..9e2683d 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeSeasonMode.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeSeasonMode.java @@ -24,15 +24,23 @@ public enum AgeSeasonMode { public static AgeSeasonMode select(AgeGenerationPlan plan, long ageSeed, boolean sereneInstalled, AgeRuntimeProfile profile) { + return select(plan, ageSeed, sereneInstalled, profile, RandomPagePolicy.UNRESTRICTED); + } + + public static AgeSeasonMode select(AgeGenerationPlan plan, long ageSeed, boolean sereneInstalled, + AgeRuntimeProfile profile, RandomPagePolicy policy) { if (profile != null && AgeBiomeSelection.isCave(profile)) return NO_SEASONS; if (hasAuthoredPage(plan)) return from(plan); - return sereneInstalled ? LegacyRankChoice.choose(new Random(ageSeed ^ RANDOM_SALT), List.of( + return sereneInstalled ? LegacyRankChoice.choose(new Random(ageSeed ^ RANDOM_SALT), policy.required(List.of( new LegacyRankChoice.Option<>(SEASONS, 1), new LegacyRankChoice.Option<>(NO_SEASONS, 2), new LegacyRankChoice.Option<>(ETERNAL_WINTER, 3), new LegacyRankChoice.Option<>(ETERNAL_SUMMER, 3), new LegacyRankChoice.Option<>(ETERNAL_FALL, 3), - new LegacyRankChoice.Option<>(ETERNAL_SPRING, 3))) : null; + new LegacyRankChoice.Option<>(ETERNAL_SPRING, 3)), + option -> net.minecraft.resources.ResourceLocation.fromNamespaceAndPath("rivencraft", + "season/" + option.value().name().toLowerCase(Locale.ROOT)), + new LegacyRankChoice.Option<>(NO_SEASONS, 2))) : null; } /** Existing Ages without a stored choice retain their old cycling behavior. */ diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeSkyProfile.java b/src/main/java/net/dnorberg/rivencraft/book/AgeSkyProfile.java index a82132a..f5202ba 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeSkyProfile.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeSkyProfile.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Random; import java.util.Set; import net.dnorberg.rivencraft.writing.SymbolSemantics; +import net.minecraft.resources.ResourceLocation; /** The ordered, deterministic sky specification for an Age. No client data is trusted here. */ public record AgeSkyProfile(Style style, int color, List bodies) { @@ -23,16 +24,25 @@ public record AgeSkyProfile(Style style, int color, List bodies) { public static AgeSkyProfile forAge(AgeDefinition age) { if (age.plan() != null) return compile(age.plan(), age.traits()); if (age.generationVersion() >= RANDOM_SKY_GENERATION_VERSION) - return random(age.seed(), age.traits()); + return random(age.seed(), age.traits(), age.randomPagePolicy()); return new AgeSkyProfile(Style.INHERITED, -1, List.of()); } public static AgeSkyProfile random(long seed, AgeTraits traits) { + return random(seed, traits, RandomPagePolicy.UNRESTRICTED); + } + + public static AgeSkyProfile random(long seed, AgeTraits traits, RandomPagePolicy policy) { Random random = new Random(seed ^ 0x534B5950524F464CL); - Style style = Style.values()[random.nextInt(Style.values().length)]; + List