Public Access
This commit is contained in:
1 parent
050a9537e9
commit
a7b2c24915
37 files changed
+378
-60
No files matched your search
@@ -64,24 +64,27 @@ public record AgeBiomeSelection(AgeTraits.BiomeDistribution distribution, List<R
|
||||
+ " is not a cave or Nether biome; use #rivencraft:cave_biomes or #minecraft:is_nether");
|
||||
}
|
||||
}
|
||||
List<ResourceLocation> eligible = eligible(registries, profile);
|
||||
if (eligible.isEmpty()) throw new IllegalArgumentException("No eligible biomes for terrain " + profile.noiseSettings());
|
||||
List<ResourceLocation> availableBiomes = eligible(registries, profile);
|
||||
if (availableBiomes.isEmpty()) throw new IllegalArgumentException("No eligible biomes for terrain " + profile.noiseSettings());
|
||||
if (distribution == AgeTraits.BiomeDistribution.NATIVE) {
|
||||
if (!authored.isEmpty()) throw new IllegalArgumentException("Native biome distribution cannot use biome pages");
|
||||
return new AgeBiomeSelection(distribution, profile.terrain() == AgeRuntimeProfile.Terrain.FLAT
|
||||
? List.of(ResourceLocation.withDefaultNamespace("plains")) : eligible);
|
||||
? List.of(ResourceLocation.withDefaultNamespace("plains")) : availableBiomes);
|
||||
}
|
||||
List<ResourceLocation> selected = new ArrayList<>(authored);
|
||||
Set<ResourceLocation> distinct = new LinkedHashSet<>(selected);
|
||||
int minimum = minimum(distribution);
|
||||
int target = minimum;
|
||||
if (authored.isEmpty()) {
|
||||
int maximum = Math.min(maximum(distribution), new LinkedHashSet<>(eligible).size());
|
||||
// The terrain's available pool is the upper bound. For Cave terrain,
|
||||
// eligible() returns the cave tag; other terrains use their preset.
|
||||
int availableBiomeCount = new LinkedHashSet<>(availableBiomes).size();
|
||||
int maximum = distribution == AgeTraits.BiomeDistribution.SINGLE ? 1 : availableBiomeCount;
|
||||
if (maximum >= minimum) {
|
||||
target += new Random(seed ^ 0x42494F4D45434F55L).nextInt(maximum - minimum + 1);
|
||||
}
|
||||
}
|
||||
List<ResourceLocation> filler = new ArrayList<>(eligible);
|
||||
List<ResourceLocation> filler = new ArrayList<>(availableBiomes);
|
||||
java.util.Collections.shuffle(filler, new Random(seed ^ 0x42494F4D45504F4FL));
|
||||
for (ResourceLocation id : filler) {
|
||||
if (distinct.size() >= target) break;
|
||||
@@ -128,12 +131,4 @@ public record AgeBiomeSelection(AgeTraits.BiomeDistribution distribution, List<R
|
||||
};
|
||||
}
|
||||
|
||||
/** Random-age limits; authored biome pages are never truncated to this maximum. */
|
||||
public static int maximum(AgeTraits.BiomeDistribution distribution) {
|
||||
return switch (distribution) {
|
||||
case NATIVE, SINGLE -> 1;
|
||||
case TILED, GRID -> 4;
|
||||
case TINY, SMALL, MEDIUM, LARGE, HUGE -> 6;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -180,6 +180,10 @@ public final class AgeDimensionService {
|
||||
|
||||
private static void applyInitialCelestialPhase(ServerLevel level, AgeDefinition age) {
|
||||
long phaseTime = (long)(age.traits().celestialPhase() * Level.TICKS_PER_DAY);
|
||||
if (AgeEnvironmentCompiler.hasWrittenPhase(age.plan())) {
|
||||
// Legacy's Nadir is midnight, while vanilla day time zero is sunrise.
|
||||
phaseTime = (phaseTime + 18_000L) % Level.TICKS_PER_DAY;
|
||||
}
|
||||
level.setDayTime(phaseTime);
|
||||
}
|
||||
|
||||
@@ -482,7 +486,7 @@ public final class AgeDimensionService {
|
||||
base.height(),
|
||||
base.logicalHeight(),
|
||||
base.infiniburn(),
|
||||
base.effectsLocation(),
|
||||
AgeEnvironmentCompiler.skyEffects(age.plan(), base.effectsLocation()),
|
||||
ambientLight,
|
||||
base.monsterSettings()
|
||||
);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package net.dnorberg.rivencraft.book;
|
||||
|
||||
import net.dnorberg.rivencraft.writing.SymbolSemantics;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.dimension.BuiltinDimensionTypes;
|
||||
|
||||
/** Resolves written environment pages without changing the seeded page-free Age path. */
|
||||
public final class AgeEnvironmentCompiler {
|
||||
private AgeEnvironmentCompiler() {}
|
||||
|
||||
public static AgeTraits apply(AgeGenerationPlan plan, AgeTraits seeded) {
|
||||
if (plan == null) return seeded;
|
||||
|
||||
AgeTraits.Lighting lighting = seeded.lighting();
|
||||
AgeTraits.Weather weather = seeded.weather();
|
||||
double period = seeded.celestialPeriod();
|
||||
float phase = seeded.celestialPhase();
|
||||
boolean normalTime = plan.symbols().stream()
|
||||
.anyMatch(symbol -> symbol.semantics().role() == SymbolSemantics.Role.CELESTIAL);
|
||||
if (normalTime) period = 1.0D;
|
||||
boolean writtenPeriod = false;
|
||||
boolean writtenPhase = false;
|
||||
float phaseDegrees = 0.0F;
|
||||
|
||||
for (AgeGenerationPlan.ResolvedSymbol symbol : plan.symbols()) {
|
||||
SymbolSemantics semantics = symbol.semantics();
|
||||
switch (semantics.role()) {
|
||||
case LIGHTING -> lighting = AgeTraits.Lighting.valueOf(
|
||||
((SymbolSemantics.ChoiceValue) semantics.value()).id().toUpperCase(java.util.Locale.ROOT));
|
||||
case WEATHER -> {
|
||||
String choice = ((SymbolSemantics.ChoiceValue) semantics.value()).id();
|
||||
if (choice.equals("snow")) {
|
||||
throw new IllegalArgumentException("Always Snow requires optional season integration");
|
||||
}
|
||||
if (choice.equals("cloudy") || choice.equals("rain")) {
|
||||
throw new IllegalArgumentException(choice + " weather needs precipitation rendering support");
|
||||
}
|
||||
weather = AgeTraits.Weather.valueOf(choice.toUpperCase(java.util.Locale.ROOT));
|
||||
}
|
||||
case PERIOD_MODIFIER -> {
|
||||
double next = ((SymbolSemantics.NumberValue) semantics.value()).amount();
|
||||
period = writtenPeriod ? (period + next) / 2.0D : next;
|
||||
writtenPeriod = true;
|
||||
}
|
||||
case PHASE_MODIFIER -> {
|
||||
float next = (float) ((SymbolSemantics.NumberValue) semantics.value()).amount();
|
||||
phaseDegrees = writtenPhase ? circularMidpoint(phaseDegrees, next) : next;
|
||||
writtenPhase = true;
|
||||
}
|
||||
case ANGLE_MODIFIER -> throw new IllegalArgumentException(
|
||||
"Sun angle pages require a custom sky renderer and are not supported yet");
|
||||
default -> { }
|
||||
}
|
||||
}
|
||||
if (writtenPhase) phase = phaseDegrees / 360.0F;
|
||||
return new AgeTraits(plan.biomeSelection() == null ? seeded.biomeDistribution()
|
||||
: plan.biomeSelection().distribution(), lighting, weather, period,
|
||||
seeded.celestialAngle(), phase);
|
||||
}
|
||||
|
||||
/** Vanilla effects are the sky styles the current renderer can honor exactly. */
|
||||
public static ResourceLocation skyEffects(AgeGenerationPlan plan, ResourceLocation fallback) {
|
||||
if (plan == null) return fallback;
|
||||
return plan.symbols().stream()
|
||||
.filter(symbol -> symbol.semantics().role() == SymbolSemantics.Role.SKY)
|
||||
.map(symbol -> ((SymbolSemantics.ChoiceValue) symbol.semantics().value()).id())
|
||||
.map(choice -> switch (choice) {
|
||||
case "overworld" -> BuiltinDimensionTypes.OVERWORLD_EFFECTS;
|
||||
case "nether" -> BuiltinDimensionTypes.NETHER_EFFECTS;
|
||||
case "end" -> BuiltinDimensionTypes.END_EFFECTS;
|
||||
default -> throw new IllegalArgumentException("Unsupported sky effect " + choice);
|
||||
}).findFirst().orElse(fallback);
|
||||
}
|
||||
|
||||
public static boolean hasWrittenPhase(AgeGenerationPlan plan) {
|
||||
return plan != null && plan.symbols().stream()
|
||||
.anyMatch(symbol -> symbol.semantics().role() == SymbolSemantics.Role.PHASE_MODIFIER);
|
||||
}
|
||||
|
||||
public static int previewSkyColor(ResourceLocation effects, int biomeSkyColor) {
|
||||
if (effects.equals(BuiltinDimensionTypes.NETHER_EFFECTS)) return 0xFF6B2725;
|
||||
if (effects.equals(BuiltinDimensionTypes.END_EFFECTS)) return 0xFF211835;
|
||||
return biomeSkyColor;
|
||||
}
|
||||
|
||||
private static float circularMidpoint(float current, float next) {
|
||||
float difference = (next - current + 540.0F) % 360.0F - 180.0F;
|
||||
return (current + difference / 2.0F + 360.0F) % 360.0F;
|
||||
}
|
||||
}
|
||||
@@ -65,10 +65,12 @@ public record AgeGenerationPlan(int version, long ageSeed, OrderedWriting writin
|
||||
AgeRuntimeProfile profile = AgeRuntimeProfiles.profiles().get(selected);
|
||||
if (profile == null) throw new IllegalArgumentException("Unsupported Age terrain profile " + selected);
|
||||
AgeBiomeSelection biomes = AgeBiomeSelection.compile(parsed, traits, profile, ageSeed, registries);
|
||||
return new AgeGenerationPlan(CURRENT_VERSION, ageSeed, writing,
|
||||
AgeGenerationPlan plan = new AgeGenerationPlan(CURRENT_VERSION, ageSeed, writing,
|
||||
parsed.symbols().stream().map(symbol -> new ResolvedSymbol(
|
||||
symbol.index(), symbol.reference(), symbol.semantics())).toList(),
|
||||
profile, biomes);
|
||||
AgeEnvironmentCompiler.apply(plan, traits);
|
||||
return plan;
|
||||
}
|
||||
|
||||
public ResourceLocation authoredTerrain() {
|
||||
|
||||
@@ -124,7 +124,7 @@ public final class AgeRegistrySavedData extends SavedData {
|
||||
createdGameTime,
|
||||
dimensionId,
|
||||
null,
|
||||
traits,
|
||||
AgeEnvironmentCompiler.apply(plan, traits),
|
||||
plan
|
||||
);
|
||||
ages.put(created.id(), created);
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
package net.dnorberg.rivencraft.book;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.writing.OrderedWriting;
|
||||
import net.dnorberg.rivencraft.writing.SymbolCatalog;
|
||||
import net.dnorberg.rivencraft.writing.SymbolReference;
|
||||
import net.dnorberg.rivencraft.writing.WritingEntry;
|
||||
import net.minecraft.gametest.framework.GameTest;
|
||||
import net.minecraft.gametest.framework.GameTestHelper;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.dimension.BuiltinDimensionTypes;
|
||||
import net.neoforged.neoforge.gametest.GameTestHolder;
|
||||
import net.neoforged.neoforge.gametest.PrefixGameTestTemplate;
|
||||
|
||||
/** Issue #34: written environment effects and the shared creative/JEI page feed. */
|
||||
@GameTestHolder("rivencraft_archive_environment")
|
||||
@PrefixGameTestTemplate(false)
|
||||
public final class EnvironmentWritingGameTests {
|
||||
private EnvironmentWritingGameTests() {}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void environmentPagesEnterCreativeAndJeiFeed(GameTestHelper helper) {
|
||||
Set<String> expected = Set.of(
|
||||
"lighting/normal", "lighting/dark", "lighting/bright",
|
||||
"sky/overworld", "sky/nether", "sky/end",
|
||||
"weather/normal", "weather/fast", "weather/slow", "weather/off",
|
||||
"weather/always", "weather/storm", "celestial/normal_time",
|
||||
"period/zero", "period/half", "period/full", "period/double",
|
||||
"phase/nadir", "phase/rising", "phase/zenith", "phase/setting");
|
||||
Set<String> creative = PageItem.creativeSymbolPages(helper.getLevel().registryAccess()).stream()
|
||||
.map(PageItem::content)
|
||||
.filter(content -> content.symbol().isPresent())
|
||||
.map(content -> content.symbol().orElseThrow().id().getPath())
|
||||
.collect(Collectors.toSet());
|
||||
helper.assertTrue(PageItem.bundledCreativeSymbols().containsAll(expected)
|
||||
&& creative.containsAll(expected),
|
||||
"Every supported environment page must enter the creative index JEI also consumes");
|
||||
for (String path : expected) {
|
||||
var id = ResourceLocation.fromNamespaceAndPath("rivencraft", path);
|
||||
helper.assertTrue(SymbolCatalog.isKnown(new SymbolReference(id))
|
||||
&& PageItem.isUsableWriting(PageItem.createBundledCreativePage(path)),
|
||||
"Environment page must resolve as valid writing: " + path);
|
||||
}
|
||||
helper.assertFalse(creative.contains("weather/snow") || creative.contains("weather/cloudy")
|
||||
|| creative.contains("weather/rain"),
|
||||
"Unsupported precipitation variants must not appear as working pages");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
@GameTest(template = "empty")
|
||||
public static void writtenEnvironmentPersistsAndPageFreeAgesStaySeeded(GameTestHelper helper) {
|
||||
OrderedWriting writing = new OrderedWriting(List.of(
|
||||
WritingEntry.linkPanel(),
|
||||
page("terrain/standard"), page("weather/off"), page("lighting/dark"),
|
||||
page("sky/end"), page("period/zero"), page("phase/rising"),
|
||||
page("celestial/normal_time")));
|
||||
AgeTraits seeded = AgeTraits.select(42L);
|
||||
var plan = AgeGenerationPlan.compile(writing, 42L, AgeGenerationProfiles.STANDARD,
|
||||
seeded, helper.getLevel().registryAccess());
|
||||
var effective = AgeEnvironmentCompiler.apply(plan, seeded);
|
||||
helper.assertValueEqual(effective.lighting(), AgeTraits.Lighting.DARK,
|
||||
"Written Dark Lighting must override the seeded lighting");
|
||||
helper.assertValueEqual(effective.weather(), AgeTraits.Weather.OFF,
|
||||
"Written No Weather must override the seeded weather");
|
||||
helper.assertTrue(effective.hasStoppedCelestialCycle(),
|
||||
"A written Zero Length page must stop the celestial cycle");
|
||||
helper.assertValueEqual(effective.celestialPhase(), 0.25F,
|
||||
"Written Rising phase must be stored as a quarter turn");
|
||||
helper.assertValueEqual(AgeEnvironmentCompiler.skyEffects(plan, BuiltinDimensionTypes.OVERWORLD_EFFECTS),
|
||||
BuiltinDimensionTypes.END_EFFECTS, "Written End Sky must select the End sky effects");
|
||||
helper.assertValueEqual(AgeEnvironmentCompiler.previewSkyColor(BuiltinDimensionTypes.END_EFFECTS, 0xFF77AAFF),
|
||||
0xFF211835, "End sky preview must not retain an Overworld biome's blue tint");
|
||||
helper.assertValueEqual(AgeEnvironmentCompiler.previewSkyColor(BuiltinDimensionTypes.NETHER_EFFECTS, 0xFF77AAFF),
|
||||
0xFF6B2725, "Nether sky preview must use its red tint");
|
||||
helper.assertValueEqual(AgeEnvironmentCompiler.previewSkyColor(BuiltinDimensionTypes.OVERWORLD_EFFECTS, 0xFF77AAFF),
|
||||
0xFF77AAFF, "Overworld sky preview must retain the destination biome tint");
|
||||
helper.assertValueEqual(AgeEnvironmentCompiler.apply(AgeGenerationPlan.load(plan.save()), seeded), effective,
|
||||
"Written environment values must survive a plan save/load round trip");
|
||||
helper.assertValueEqual(AgeEnvironmentCompiler.apply(null, seeded), seeded,
|
||||
"Page-free Ages must retain their seeded environment without recompilation");
|
||||
|
||||
var player = helper.makeMockServerPlayerInLevel();
|
||||
var reference = new AgeReference(UUID.randomUUID(), "Written Environment");
|
||||
var target = AgeDimensionService.resolve(player, reference, writing).orElseThrow();
|
||||
var definition = AgeRegistrySavedData.get(helper.getLevel()).find(reference.id()).orElseThrow();
|
||||
helper.assertValueEqual(definition.traits(), AgeEnvironmentCompiler.apply(
|
||||
definition.plan(), AgeTraits.select(definition.seed())),
|
||||
"The effective authored environment must persist with the Age definition");
|
||||
helper.runAfterDelay(1, () -> {
|
||||
var level = helper.getLevel().getServer().getLevel(target.location().dimension());
|
||||
helper.assertTrue(level != null, "Written environment Age must load");
|
||||
helper.assertFalse(level.dimensionType().hasSkyLight(), "Dark Lighting must remove skylight");
|
||||
helper.assertValueEqual(level.dimensionType().effectsLocation(), BuiltinDimensionTypes.END_EFFECTS,
|
||||
"The runtime Age must render the written End sky");
|
||||
helper.assertValueEqual(level.getDayTimePerTick(), Float.MIN_VALUE,
|
||||
"Zero Length must use the non-bouncing stopped clock");
|
||||
helper.assertTrue(Math.floorMod(level.getDayTime(), 24_000L) < 5L,
|
||||
"Legacy Rising phase must begin at vanilla sunrise");
|
||||
level.setWeatherParameters(0, 6_000, true, true);
|
||||
helper.runAfterDelay(2, () -> {
|
||||
helper.assertFalse(level.isRaining() || level.isThundering(),
|
||||
"Written No Weather must clear rain and thunder in this Age");
|
||||
helper.succeed();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static WritingEntry page(String path) {
|
||||
return WritingEntry.symbol(new SymbolReference(ResourceLocation.fromNamespaceAndPath("rivencraft", path)));
|
||||
}
|
||||
}
|
||||
@@ -147,16 +147,42 @@ public final class TerrainWritingGameTests {
|
||||
var randomWriting = net.dnorberg.rivencraft.writing.WritingGrammar.parse(OrderedWriting.EMPTY);
|
||||
var traits = new AgeTraits(AgeTraits.BiomeDistribution.TINY, AgeTraits.Lighting.NORMAL,
|
||||
AgeTraits.Weather.NORMAL, 1.0D, 0.0F, 0.0F);
|
||||
int availableBiomeCount = new java.util.LinkedHashSet<>(AgeBiomeSelection.eligible(registries, profile)).size();
|
||||
java.util.Set<Integer> counts = new java.util.HashSet<>();
|
||||
for (long seed = 0; seed < 64; seed++) {
|
||||
var random = AgeBiomeSelection.compile(randomWriting, traits, profile, seed, registries);
|
||||
helper.assertTrue(random.biomes().size() >= 2 && random.biomes().size() <= 6,
|
||||
"Random Tiny ages must select between two and six distinct biomes");
|
||||
helper.assertTrue(random.biomes().size() >= 2 && random.biomes().size() <= availableBiomeCount,
|
||||
"Random Tiny ages must not exceed their available biome pool");
|
||||
helper.assertValueEqual(random, AgeBiomeSelection.compile(randomWriting, traits, profile, seed, registries),
|
||||
"The random count and chosen biomes must be deterministic for an Age seed");
|
||||
counts.add(random.biomes().size());
|
||||
}
|
||||
helper.assertTrue(counts.size() > 1, "Random Ages must vary their biome count across seeds");
|
||||
if (availableBiomeCount > 6) {
|
||||
helper.assertTrue(counts.stream().anyMatch(count -> count > 6),
|
||||
"The available biome pool, not the old fixed cap, must bound random Ages");
|
||||
}
|
||||
|
||||
var tiledTraits = new AgeTraits(AgeTraits.BiomeDistribution.TILED, AgeTraits.Lighting.NORMAL,
|
||||
AgeTraits.Weather.NORMAL, 1.0D, 0.0F, 0.0F);
|
||||
if (availableBiomeCount > 4) {
|
||||
boolean exceededOldCap = false;
|
||||
for (long seed = 0; seed < 64; seed++) {
|
||||
var random = AgeBiomeSelection.compile(randomWriting, tiledTraits, profile, seed, registries);
|
||||
helper.assertTrue(random.biomes().size() >= 2 && random.biomes().size() <= availableBiomeCount,
|
||||
"Random Tiled ages must not exceed their available biome pool");
|
||||
exceededOldCap |= random.biomes().size() > 4;
|
||||
}
|
||||
helper.assertTrue(exceededOldCap, "Tiled Ages must also be able to exceed the old fixed cap");
|
||||
}
|
||||
|
||||
var caveProfile = AgeRuntimeProfiles.resolve(AgeGenerationProfiles.CAVE);
|
||||
int caveBiomeCount = new java.util.LinkedHashSet<>(AgeBiomeSelection.eligible(registries, caveProfile)).size();
|
||||
for (long seed = 0; seed < 64; seed++) {
|
||||
var random = AgeBiomeSelection.compile(randomWriting, traits, caveProfile, seed, registries);
|
||||
helper.assertTrue(random.biomes().size() >= 2 && random.biomes().size() <= caveBiomeCount,
|
||||
"Random Cave ages must use the cave biome pool as their upper bound");
|
||||
}
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
|
||||
@@ -247,8 +247,6 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
|
||||
x, top + 39, 126, color);
|
||||
drawWrappedWithoutShadow(graphics, Component.literal(display.id().toString()),
|
||||
x, top + 64, 126, color);
|
||||
drawWrappedWithoutShadow(graphics, Component.translatable("screen.rivencraft.age.pages_not_applied"),
|
||||
x, top + 137, 126);
|
||||
}
|
||||
|
||||
private void turnPage(int direction) {
|
||||
|
||||
@@ -421,9 +421,11 @@ public final class LinkingGameTests {
|
||||
}
|
||||
if (profile.equals(AgeGenerationProfiles.AMPLIFIED)) {
|
||||
var biomeSource = level.getChunkSource().getGenerator().getBiomeSource();
|
||||
int availableBiomes = AgeBiomeSelection.eligible(helper.getLevel().registryAccess(),
|
||||
AgeRuntimeProfiles.resolve(profile)).size();
|
||||
helper.assertTrue(biomeSource.possibleBiomes().size() >= 2
|
||||
&& biomeSource.possibleBiomes().size() <= 4,
|
||||
"Random Tiled distribution must use two to four seeded biomes");
|
||||
&& biomeSource.possibleBiomes().size() <= availableBiomes,
|
||||
"Random Tiled distribution must stay within the available biome pool");
|
||||
var origin = biomeSource.getNoiseBiome(0, 0, 0, null);
|
||||
var eastTile = biomeSource.getNoiseBiome(64, 0, 0, null);
|
||||
helper.assertFalse(origin.equals(eastTile),
|
||||
@@ -431,9 +433,11 @@ public final class LinkingGameTests {
|
||||
}
|
||||
if (profile.equals(AgeGenerationProfiles.ISLAND)) {
|
||||
var biomeSource = level.getChunkSource().getGenerator().getBiomeSource();
|
||||
int availableBiomes = AgeBiomeSelection.eligible(helper.getLevel().registryAccess(),
|
||||
AgeRuntimeProfiles.resolve(profile)).size();
|
||||
helper.assertTrue(biomeSource.possibleBiomes().size() >= 2
|
||||
&& biomeSource.possibleBiomes().size() <= 4,
|
||||
"Random Grid distribution must use two to four seeded biomes");
|
||||
&& biomeSource.possibleBiomes().size() <= availableBiomes,
|
||||
"Random Grid distribution must stay within the available biome pool");
|
||||
var origin = biomeSource.getNoiseBiome(0, 0, 0, null);
|
||||
var insideOriginChunk = biomeSource.getNoiseBiome(3, 0, 3, null);
|
||||
var eastChunk = biomeSource.getNoiseBiome(4, 0, 0, null);
|
||||
@@ -444,11 +448,13 @@ public final class LinkingGameTests {
|
||||
}
|
||||
if (profile.equals(AgeGenerationProfiles.CAVE)) {
|
||||
var biomeSource = level.getChunkSource().getGenerator().getBiomeSource();
|
||||
int caveBiomeCount = AgeBiomeSelection.eligible(helper.getLevel().registryAccess(),
|
||||
AgeRuntimeProfiles.resolve(profile)).size();
|
||||
helper.assertTrue(biomeSource instanceof net.dnorberg.rivencraft.world.ScaledBiomeSource,
|
||||
"Tiny Biome Distribution must use the serializable scaled biome source");
|
||||
helper.assertTrue(biomeSource.possibleBiomes().size() >= 2
|
||||
&& biomeSource.possibleBiomes().size() <= 3,
|
||||
"Random cave Tiny distribution must use two to three eligible biomes");
|
||||
&& biomeSource.possibleBiomes().size() <= caveBiomeCount,
|
||||
"Random cave Tiny distribution must use only eligible cave biomes");
|
||||
java.util.Set<net.minecraft.core.Holder<net.minecraft.world.level.biome.Biome>> sampled =
|
||||
new java.util.HashSet<>();
|
||||
for (int x = -32; x <= 32; x += 4) {
|
||||
@@ -520,11 +526,13 @@ public final class LinkingGameTests {
|
||||
continue;
|
||||
}
|
||||
var biomeSource = level.getChunkSource().getGenerator().getBiomeSource();
|
||||
int availableBiomes = AgeBiomeSelection.eligible(helper.getLevel().registryAccess(),
|
||||
AgeRuntimeProfiles.resolve(AgeGenerationProfiles.STANDARD)).size();
|
||||
helper.assertTrue(biomeSource instanceof net.dnorberg.rivencraft.world.ScaledBiomeSource,
|
||||
distribution + " Biome Distribution must use the serializable scaled biome source");
|
||||
helper.assertTrue(biomeSource.possibleBiomes().size() >= 2
|
||||
&& biomeSource.possibleBiomes().size() <= 6,
|
||||
distribution + " random distribution must use two to six seeded biomes");
|
||||
&& biomeSource.possibleBiomes().size() <= availableBiomes,
|
||||
distribution + " random distribution must stay within the available biome pool");
|
||||
helper.assertValueEqual(
|
||||
((net.dnorberg.rivencraft.world.ScaledBiomeSource)biomeSource).scale(),
|
||||
scale,
|
||||
|
||||
@@ -7,6 +7,7 @@ import net.dnorberg.rivencraft.book.AgeRegistrySavedData;
|
||||
import net.dnorberg.rivencraft.book.AgeDimensionService;
|
||||
import net.dnorberg.rivencraft.book.AgeGenerationPlan;
|
||||
import net.dnorberg.rivencraft.book.AgeGenerationProfiles;
|
||||
import net.dnorberg.rivencraft.book.AgeEnvironmentCompiler;
|
||||
import net.dnorberg.rivencraft.block.entity.BookStandBlockEntity;
|
||||
import net.dnorberg.rivencraft.client.ClientLinkPreviewHandler;
|
||||
import net.dnorberg.rivencraft.client.ClientBookInteractionHandler;
|
||||
@@ -313,15 +314,10 @@ public final class ModNetworking {
|
||||
}
|
||||
}
|
||||
|
||||
/** Vanilla Nether and End renderers do not use their biomes' blue Overworld sky tint. */
|
||||
/** Sky effects, not dimension IDs, decide the backdrop for custom Ages. */
|
||||
private static int previewSkyColor(ServerLevel level, BlockPos origin) {
|
||||
if (level.dimension().equals(Level.NETHER)) {
|
||||
return 0xFF6B2725;
|
||||
}
|
||||
if (level.dimension().equals(Level.END)) {
|
||||
return 0xFF211835;
|
||||
}
|
||||
return level.getBiome(origin).value().getSkyColor();
|
||||
return AgeEnvironmentCompiler.previewSkyColor(level.dimensionType().effectsLocation(),
|
||||
level.getBiome(origin).value().getSkyColor());
|
||||
}
|
||||
|
||||
private static InteractionHand hand(boolean offhand) {
|
||||
|
||||
@@ -77,6 +77,7 @@ public record SymbolSemantics(Role role, Value value) {
|
||||
"native", "single", "tiled", "grid", "tiny", "small", "medium", "large", "huge"),
|
||||
BIOME("biome", Kind.RESOURCE),
|
||||
LIGHTING("lighting", Kind.CHOICE, "normal", "dark", "bright"),
|
||||
SKY("sky", Kind.CHOICE, "overworld", "nether", "end"),
|
||||
WEATHER("weather", Kind.CHOICE,
|
||||
"normal", "fast", "slow", "off", "always", "cloudy", "rain", "snow", "storm"),
|
||||
CELESTIAL("celestial", Kind.CHOICE, "normal_time"),
|
||||
|
||||
@@ -69,6 +69,12 @@ public final class WritingGrammar {
|
||||
Role role = semantics.orElseThrow().role();
|
||||
ParsedSymbol parsed = new ParsedSymbol(index, reference, semantics.orElseThrow());
|
||||
symbols.add(parsed);
|
||||
if (role == Role.ANGLE_MODIFIER || role == Role.WEATHER
|
||||
&& parsed.semantics().value() instanceof ChoiceValue choice
|
||||
&& Set.of("snow", "cloudy", "rain").contains(choice.id())) {
|
||||
diagnostics.add(new Diagnostic(Code.UNSUPPORTED_ENVIRONMENT, index,
|
||||
"This environment effect needs renderer or precipitation support"));
|
||||
}
|
||||
if (singular(role)) {
|
||||
ParsedSymbol previous = singular.putIfAbsent(role, parsed);
|
||||
if (previous != null) {
|
||||
@@ -127,7 +133,7 @@ public final class WritingGrammar {
|
||||
|
||||
private static boolean singular(Role role) {
|
||||
return switch (role) {
|
||||
case TERRAIN, BIOME_CONTROLLER, WEATHER, LIGHTING, CELESTIAL -> true;
|
||||
case TERRAIN, BIOME_CONTROLLER, WEATHER, LIGHTING, SKY, CELESTIAL -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
@@ -143,16 +149,17 @@ public final class WritingGrammar {
|
||||
case BIOME_CONTROLLER -> 2;
|
||||
case WEATHER -> 3;
|
||||
case LIGHTING -> 4;
|
||||
case PERIOD_MODIFIER, ANGLE_MODIFIER, PHASE_MODIFIER -> 5;
|
||||
case CELESTIAL -> 6;
|
||||
case FEATURE -> 7;
|
||||
case SKY -> 5;
|
||||
case PERIOD_MODIFIER, ANGLE_MODIFIER, PHASE_MODIFIER -> 6;
|
||||
case CELESTIAL -> 7;
|
||||
case FEATURE -> 8;
|
||||
};
|
||||
}
|
||||
|
||||
public enum Code {
|
||||
TOO_MANY, MISSING_PANEL, PANEL_ORDER, DUPLICATE_PANEL, UNRESOLVED, UNSUPPORTED,
|
||||
DUPLICATE, CONFLICT, OUT_OF_ORDER, MISPLACED_MODIFIER, INCOMPLETE, UNSUPPORTED_PROFILE,
|
||||
UNKNOWN_BIOME, UNSUPPORTED_BIOME
|
||||
UNKNOWN_BIOME, UNSUPPORTED_BIOME, UNSUPPORTED_ENVIRONMENT
|
||||
}
|
||||
|
||||
public record Diagnostic(Code code, int index, String message) {
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"screen.rivencraft.book_binder.grammar.unresolved": "Page %s: missing symbol definition; restore its data pack or remove it",
|
||||
"screen.rivencraft.book_binder.grammar.unsupported": "Page %s: this symbol needs semantic schema v2; update its data pack or remove it",
|
||||
"screen.rivencraft.book_binder.grammar.unsupported_profile": "Page %s: its Age profile is unavailable; restore the data pack or remove this page",
|
||||
"screen.rivencraft.book_binder.grammar.unsupported_environment": "Page %s: this weather or sky effect is not supported yet",
|
||||
"screen.rivencraft.book_binder.grammar.duplicate": "Page %s: remove the duplicate symbol",
|
||||
"screen.rivencraft.book_binder.grammar.conflict": "Page %s: replace the conflicting symbol",
|
||||
"screen.rivencraft.book_binder.grammar.out_of_order": "Page %s: move this symbol before later categories",
|
||||
@@ -87,7 +88,6 @@
|
||||
"screen.rivencraft.age.pages": "Pages",
|
||||
"screen.rivencraft.age.details": "Details",
|
||||
"screen.rivencraft.age.pages_count": "Writing %s/%s",
|
||||
"screen.rivencraft.age.pages_not_applied": "Symbols not applied yet",
|
||||
"screen.rivencraft.age.trait.biomes": "Biomes: %s",
|
||||
"screen.rivencraft.age.trait.lighting": "Lighting: %s",
|
||||
"screen.rivencraft.age.trait.weather": "Weather: %s",
|
||||
@@ -167,10 +167,46 @@
|
||||
"symbol.rivencraft.biome.huge.description": "Distribute selected biomes in huge regions.",
|
||||
"symbol.rivencraft.lighting.normal": "Normal Lighting",
|
||||
"symbol.rivencraft.lighting.normal.description": "Use normal skylight and ambient brightness.",
|
||||
"symbol.rivencraft.lighting.dark": "Dark Lighting",
|
||||
"symbol.rivencraft.lighting.dark.description": "Remove skylight from this Age.",
|
||||
"symbol.rivencraft.lighting.bright": "Bright Lighting",
|
||||
"symbol.rivencraft.lighting.bright.description": "Increase this Age's ambient brightness.",
|
||||
"symbol.rivencraft.sky.overworld": "Overworld Sky",
|
||||
"symbol.rivencraft.sky.overworld.description": "Use the Overworld sky and biome sky tint.",
|
||||
"symbol.rivencraft.sky.nether": "Nether Sky",
|
||||
"symbol.rivencraft.sky.nether.description": "Use the Nether sky and red preview background.",
|
||||
"symbol.rivencraft.sky.end": "End Sky",
|
||||
"symbol.rivencraft.sky.end.description": "Use the End sky and dark preview background.",
|
||||
"symbol.rivencraft.weather.normal": "Normal Weather",
|
||||
"symbol.rivencraft.weather.normal.description": "Use the normal weather cycle.",
|
||||
"symbol.rivencraft.weather.fast": "Fast Weather",
|
||||
"symbol.rivencraft.weather.fast.description": "Change weather more quickly.",
|
||||
"symbol.rivencraft.weather.slow": "Slow Weather",
|
||||
"symbol.rivencraft.weather.slow.description": "Change weather more slowly.",
|
||||
"symbol.rivencraft.weather.off": "No Weather",
|
||||
"symbol.rivencraft.weather.off.description": "Keep the Age clear.",
|
||||
"symbol.rivencraft.weather.always": "Always Weather",
|
||||
"symbol.rivencraft.weather.always.description": "Keep the Age rainy.",
|
||||
"symbol.rivencraft.weather.storm": "Storm Weather",
|
||||
"symbol.rivencraft.weather.storm.description": "Keep the Age rainy and thundering.",
|
||||
"symbol.rivencraft.celestial.normal_time": "Normal Time",
|
||||
"symbol.rivencraft.celestial.normal_time.description": "Use a normal-length celestial cycle.",
|
||||
"symbol.rivencraft.period.zero": "Zero Length",
|
||||
"symbol.rivencraft.period.zero.description": "Stop the celestial cycle at its written phase.",
|
||||
"symbol.rivencraft.period.half": "Half Length",
|
||||
"symbol.rivencraft.period.half.description": "Use a half-length celestial cycle.",
|
||||
"symbol.rivencraft.period.full": "Full Length",
|
||||
"symbol.rivencraft.period.full.description": "Use a normal-length celestial cycle.",
|
||||
"symbol.rivencraft.period.double": "Double Length",
|
||||
"symbol.rivencraft.period.double.description": "Use a double-length celestial cycle.",
|
||||
"symbol.rivencraft.phase.nadir": "Nadir Phase",
|
||||
"symbol.rivencraft.phase.nadir.description": "Set the celestial phase to nadir.",
|
||||
"symbol.rivencraft.phase.rising": "Rising Phase",
|
||||
"symbol.rivencraft.phase.rising.description": "Set the celestial phase to rising.",
|
||||
"symbol.rivencraft.phase.zenith": "Zenith Phase",
|
||||
"symbol.rivencraft.phase.zenith.description": "Set the celestial phase to zenith.",
|
||||
"symbol.rivencraft.phase.setting": "Setting Phase",
|
||||
"symbol.rivencraft.phase.setting.description": "Set the celestial phase to setting.",
|
||||
"symbol.rivencraft.feature.caves": "Caves",
|
||||
"symbol.rivencraft.feature.caves.description": "Describe underground cave features.",
|
||||
"generator.rivencraft.standard": "Standard",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.lighting.bright","description":"symbol.rivencraft.lighting.bright.description","category":"rivencraft:lighting","semantics":{"role":"lighting","value":"bright"},"poem":"Ethereal Sustain Cycle Balance"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.lighting.dark","description":"symbol.rivencraft.lighting.dark.description","category":"rivencraft:lighting","semantics":{"role":"lighting","value":"dark"},"poem":"Ethereal Void Cycle Balance"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.period.double","description":"symbol.rivencraft.period.double.description","category":"rivencraft:celestial","semantics":{"role":"period_modifier","value":2.0},"poem":"Transform Time Cycle Dynamic"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.period.full","description":"symbol.rivencraft.period.full.description","category":"rivencraft:celestial","semantics":{"role":"period_modifier","value":1.0},"poem":"Transform Time Cycle Balance"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.period.half","description":"symbol.rivencraft.period.half.description","category":"rivencraft:celestial","semantics":{"role":"period_modifier","value":0.5},"poem":"Transform Time Cycle Constraint"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.period.zero","description":"symbol.rivencraft.period.zero.description","category":"rivencraft:celestial","semantics":{"role":"period_modifier","value":0.0},"poem":"Transform Time Void Balance"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.phase.nadir","description":"symbol.rivencraft.phase.nadir.description","category":"rivencraft:celestial","semantics":{"role":"phase_modifier","value":0.0},"poem":"Transform Time Void Tradition"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.phase.rising","description":"symbol.rivencraft.phase.rising.description","category":"rivencraft:celestial","semantics":{"role":"phase_modifier","value":90.0},"poem":"Transform Time Flow Tradition"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.phase.setting","description":"symbol.rivencraft.phase.setting.description","category":"rivencraft:celestial","semantics":{"role":"phase_modifier","value":270.0},"poem":"Transform Time Nature Tradition"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.phase.zenith","description":"symbol.rivencraft.phase.zenith.description","category":"rivencraft:celestial","semantics":{"role":"phase_modifier","value":180.0},"poem":"Transform Time Ethereal Tradition"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.sky.end","description":"symbol.rivencraft.sky.end.description","category":"rivencraft:sky","semantics":{"role":"sky","value":"end"},"poem":"Ethereal Void Sky Balance"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.sky.nether","description":"symbol.rivencraft.sky.nether.description","category":"rivencraft:sky","semantics":{"role":"sky","value":"nether"},"poem":"Ethereal Void Sky Transform"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.sky.overworld","description":"symbol.rivencraft.sky.overworld.description","category":"rivencraft:sky","semantics":{"role":"sky","value":"overworld"},"poem":"Ethereal Nature Sky Balance"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.weather.always","description":"symbol.rivencraft.weather.always.description","category":"rivencraft:weather","semantics":{"role":"weather","value":"always"},"poem":"Sustain Flow Cycle Tradition"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.weather.fast","description":"symbol.rivencraft.weather.fast.description","category":"rivencraft:weather","semantics":{"role":"weather","value":"fast"},"poem":"Sustain Dynamic Flow Transform"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.weather.off","description":"symbol.rivencraft.weather.off.description","category":"rivencraft:weather","semantics":{"role":"weather","value":"off"},"poem":"Sustain Void Flow Balance"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.weather.slow","description":"symbol.rivencraft.weather.slow.description","category":"rivencraft:weather","semantics":{"role":"weather","value":"slow"},"poem":"Sustain Dynamic Flow Balance"}
|
||||
@@ -0,0 +1 @@
|
||||
{"schema_version":2,"name":"symbol.rivencraft.weather.storm","description":"symbol.rivencraft.weather.storm.description","category":"rivencraft:weather","semantics":{"role":"weather","value":"storm"},"poem":"Sustain Nature Flow Dynamic"}
|
||||
Reference in new issue
Block a user