From 3ac4c499ab4e83b9b8598fe51db0e34b79b0a2c8 Mon Sep 17 00:00:00 2001 From: Dustin Norberg Date: Wed, 21 Oct 2026 23:56:58 -0400 Subject: [PATCH] Add authored Age skies and celestial pages (#44) --- README.md | 11 ++ gradle.properties | 2 +- .../entity/BookReceptacleBlockEntity.java | 13 ++ .../rivencraft/book/AgeDefinition.java | 3 +- .../rivencraft/book/AgeDimensionService.java | 16 +- .../book/AgeEnvironmentCompiler.java | 58 +++++- .../rivencraft/book/AgeGenerationPlan.java | 2 +- .../rivencraft/book/AgeInstability.java | 18 +- .../rivencraft/book/AgeRegistrySavedData.java | 2 +- .../rivencraft/book/AgeSkyProfile.java | 181 ++++++++++++++++++ .../book/EnvironmentWritingGameTests.java | 104 +++++++++- .../client/ClientAgeCelestialAngles.java | 35 ++++ .../rivencraft/client/ClientEvents.java | 28 +++ .../client/RivencraftSkyEffects.java | 145 ++++++++++++++ .../event/AgeCelestialAngleSync.java | 8 +- .../mixin/LevelSkyBrightnessMixin.java | 40 ++++ .../client/ClientLevelSkyColorMixin.java | 40 ++++ .../mixin/client/LevelRendererMixin.java | 17 ++ .../network/AgeCelestialAnglePayload.java | 34 +++- .../rivencraft/network/ModNetworking.java | 17 +- .../network/RequestAgeSkyPayload.java | 24 +++ .../rivencraft/writing/SymbolSemantics.java | 12 +- .../rivencraft/writing/WritingGrammar.java | 18 +- .../assets/rivencraft/lang/en_us.json | 56 ++++++ .../models/block/crystal_portal.json | 2 +- .../rivencraft/symbols/angle/east.json | 1 + .../rivencraft/symbols/angle/north.json | 1 + .../rivencraft/symbols/angle/south.json | 1 + .../rivencraft/symbols/angle/west.json | 1 + .../rivencraft/symbols/celestial/moon.json | 1 + .../rivencraft/symbols/celestial/sun.json | 1 + .../symbols/celestial_color/black.json | 1 + .../symbols/celestial_color/blue.json | 1 + .../symbols/celestial_color/green.json | 1 + .../symbols/celestial_color/orange.json | 1 + .../symbols/celestial_color/purple.json | 1 + .../symbols/celestial_color/red.json | 1 + .../symbols/celestial_color/white.json | 1 + .../symbols/celestial_color/yellow.json | 1 + .../symbols/direction/clockwise.json | 1 + .../symbols/direction/counterclockwise.json | 1 + .../rivencraft/symbols/size/large.json | 1 + .../rivencraft/symbols/size/normal.json | 1 + .../rivencraft/symbols/size/small.json | 1 + .../rivencraft/symbols/sky/static.json | 1 + .../rivencraft/symbols/sky_color/black.json | 1 + .../rivencraft/symbols/sky_color/blue.json | 1 + .../rivencraft/symbols/sky_color/green.json | 1 + .../rivencraft/symbols/sky_color/orange.json | 1 + .../rivencraft/symbols/sky_color/purple.json | 1 + .../rivencraft/symbols/sky_color/red.json | 1 + .../rivencraft/symbols/sky_color/white.json | 1 + .../rivencraft/symbols/sky_color/yellow.json | 1 + src/main/resources/rivencraft.mixins.json | 2 + 54 files changed, 880 insertions(+), 36 deletions(-) create mode 100644 src/main/java/net/dnorberg/rivencraft/book/AgeSkyProfile.java create mode 100644 src/main/java/net/dnorberg/rivencraft/client/RivencraftSkyEffects.java create mode 100644 src/main/java/net/dnorberg/rivencraft/mixin/LevelSkyBrightnessMixin.java create mode 100644 src/main/java/net/dnorberg/rivencraft/mixin/client/ClientLevelSkyColorMixin.java create mode 100644 src/main/java/net/dnorberg/rivencraft/network/RequestAgeSkyPayload.java create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/angle/east.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/angle/north.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/angle/south.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/angle/west.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/celestial/moon.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/celestial/sun.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/black.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/blue.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/green.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/orange.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/purple.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/red.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/white.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/yellow.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/direction/clockwise.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/direction/counterclockwise.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/size/large.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/size/normal.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/size/small.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/sky/static.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/black.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/blue.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/green.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/orange.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/purple.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/red.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/white.json create mode 100644 src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/yellow.json diff --git a/README.md b/README.md index b3d8979..be26d23 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,17 @@ the top or underside of one of its blocks. Insert a bound Linking or Descriptive Book to activate it; the book remains visible on the receptacle. A Linking Book aimed at its own dimension still opens a visual portal, but travel requires Intra Linking. +Sky writing includes eight color pages and a Static Sky page. Multiple sky-color +pages blend their colors, tinting the Age sky, book preview, and the fallback +surface of portals linked there. Multiple celestial-color pages blend for the +next written sun or moon. An Age may have no suns or moons at all; +Static Sky animates the sky with television-like noise. Sun and Moon pages add +independent bodies; preceding color, size, angle, period, phase, and direction +pages configure the next body. The third and each subsequent sun add increasing +instability. Newly generated page-free Ages also choose a deterministic sky, +color, and celestial arrangement from their Age seed; older saved Ages retain +their previous sky. + Craft a Book Binder from three iron ingots and five planks. Put leather or an empty Notebook in the left cover slot. Enter a title, then place a Link Panel Page first in the horizontal page strip. Click a page in the strip with a diff --git a/gradle.properties b/gradle.properties index 94e38b2..b2894e0 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=0.6.8.4 +mod_version=0.6.8.7 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/block/entity/BookReceptacleBlockEntity.java b/src/main/java/net/dnorberg/rivencraft/block/entity/BookReceptacleBlockEntity.java index 9d0d3a0..b3166f6 100644 --- a/src/main/java/net/dnorberg/rivencraft/block/entity/BookReceptacleBlockEntity.java +++ b/src/main/java/net/dnorberg/rivencraft/block/entity/BookReceptacleBlockEntity.java @@ -1,6 +1,7 @@ package net.dnorberg.rivencraft.block.entity; import net.dnorberg.rivencraft.book.LinkTarget; +import net.dnorberg.rivencraft.book.AgeEnvironmentCompiler; import net.dnorberg.rivencraft.registry.ModBlockEntities; import net.dnorberg.rivencraft.portal.CrystalPortalNetwork; import net.minecraft.nbt.NbtOps; @@ -20,8 +21,10 @@ import net.neoforged.fml.ModList; public final class BookReceptacleBlockEntity extends BlockEntity { private static final String BOOK_TAG = "Book"; private static final String DESTINATION_TAG = "Destination"; + private static final String SKY_COLOR_TAG = "SkyColor"; private ItemStack book = ItemStack.EMPTY; private LinkTarget destination; + private int skyColor = 0xFFFFFF; private boolean restoreView = true; public BookReceptacleBlockEntity(BlockPos pos, BlockState state) { @@ -30,6 +33,7 @@ public final class BookReceptacleBlockEntity extends BlockEntity { public ItemStack getBook() { return book; } public LinkTarget destination() { return destination; } + public int skyColor() { return skyColor; } public void setBook(ItemStack stack) { book = stack.copyWithCount(1); @@ -38,6 +42,12 @@ public final class BookReceptacleBlockEntity extends BlockEntity { public void setDestination(LinkTarget target) { destination = target; + skyColor = 0xFFFFFF; + if (target != null && level instanceof ServerLevel server) { + ServerLevel targetLevel = server.getServer().getLevel(target.location().dimension()); + if (targetLevel != null) skyColor = AgeEnvironmentCompiler.previewSkyColor( + targetLevel, target.location().pos()) & 0xFFFFFF; + } sync(); } @@ -45,6 +55,7 @@ public final class BookReceptacleBlockEntity extends BlockEntity { ItemStack result = book; book = ItemStack.EMPTY; destination = null; + skyColor = 0xFFFFFF; sync(); return result; } @@ -63,6 +74,7 @@ public final class BookReceptacleBlockEntity extends BlockEntity { : ItemStack.EMPTY; destination = tag.contains(DESTINATION_TAG) ? LinkTarget.CODEC.parse(NbtOps.INSTANCE, tag.get(DESTINATION_TAG)).result().orElse(null) : null; + skyColor = tag.contains(SKY_COLOR_TAG) ? tag.getInt(SKY_COLOR_TAG) & 0xFFFFFF : 0xFFFFFF; restoreView = true; } @@ -72,6 +84,7 @@ public final class BookReceptacleBlockEntity extends BlockEntity { if (!book.isEmpty()) tag.put(BOOK_TAG, book.save(registries)); if (destination != null) LinkTarget.CODEC.encodeStart(NbtOps.INSTANCE, destination) .result().ifPresent(encoded -> tag.put(DESTINATION_TAG, encoded)); + if (destination != null) tag.putInt(SKY_COLOR_TAG, skyColor); } @Override public Packet getUpdatePacket() { diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeDefinition.java b/src/main/java/net/dnorberg/rivencraft/book/AgeDefinition.java index 2925751..7f279f4 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeDefinition.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeDefinition.java @@ -28,7 +28,8 @@ public record AgeDefinition( public static final ResourceLocation RANDOM_VANILLA_PROFILE = ResourceLocation.fromNamespaceAndPath( Rivencraft.MOD_ID, "random_vanilla" ); - public static final int CURRENT_GENERATION_VERSION = 16; + public static final int LEGACY_GENERATOR_CUTOFF = 16; + public static final int CURRENT_GENERATION_VERSION = AgeSkyProfile.RANDOM_SKY_GENERATION_VERSION; public AgeDefinition { Objects.requireNonNull(id, "id"); diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java b/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java index a1c51c1..6aeae8d 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeDimensionService.java @@ -352,7 +352,7 @@ public final class AgeDimensionService { server.registryAccess().lookupOrThrow(Registries.STRUCTURE_SET), server.registryAccess().lookupOrThrow(Registries.PLACED_FEATURE) ); - return age.generationVersion() < AgeDefinition.CURRENT_GENERATION_VERSION + return age.generationVersion() < AgeDefinition.LEGACY_GENERATOR_CUTOFF ? new FlatLevelSource(settings) : new AgeFlatChunkGenerator(biomeSource, settings, features, age.seed(), libraryAgeSymbols); } if (profile.terrain() == AgeRuntimeProfile.Terrain.VOID) { @@ -362,7 +362,7 @@ public final class AgeDimensionService { List.of() ); settings.updateLayers(); - return age.generationVersion() < AgeDefinition.CURRENT_GENERATION_VERSION + return age.generationVersion() < AgeDefinition.LEGACY_GENERATOR_CUTOFF ? new FlatLevelSource(settings) : new AgeFlatChunkGenerator(biomeSource, settings, features, age.seed(), libraryAgeSymbols); } @@ -372,7 +372,7 @@ public final class AgeDimensionService { NoiseGeneratorSettings baseSettings = noiseSettings.getOrThrow(settingsKey).value(); Holder settings = Holder.direct(withBiomeMaterialRules(baseSettings)); boolean caveTerrain = AgeBiomeSelection.isCave(profile); - return age.generationVersion() < AgeDefinition.CURRENT_GENERATION_VERSION + return age.generationVersion() < AgeDefinition.LEGACY_GENERATOR_CUTOFF ? new NoiseBasedChunkGenerator(biomeSource, settings) : new AgeFeatureChunkGenerator(biomeSource, settings, features, age.seed(), caveTerrain, caveTerrain ? TerraBlenderBiomes.nether(server.registryAccess()) : List.of(), @@ -455,7 +455,7 @@ public final class AgeDimensionService { AgeDefinition age, AgeRuntimeProfile profile ) { - if (age.generationVersion() < AgeDefinition.CURRENT_GENERATION_VERSION) { + if (age.generationVersion() < AgeDefinition.LEGACY_GENERATOR_CUTOFF) { return legacyBiomeSource(server, age, profile); } AgeBiomeSelection selection = age.plan() != null && age.plan().biomeSelection() != null @@ -558,7 +558,9 @@ public final class AgeDimensionService { 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 = base.hasSkyLight() && lighting != AgeTraits.Lighting.DARK; + AgeSkyProfile sky = AgeSkyProfile.forAge(age); + boolean hasSkyLight = (base.hasSkyLight() || !sky.bodies().isEmpty()) + && lighting != AgeTraits.Lighting.DARK && !age.caveTerrain(); float ambientLight = lighting == AgeTraits.Lighting.BRIGHT ? 0.25F : 0.0F; return new DimensionType( base.fixedTime(), @@ -573,7 +575,9 @@ public final class AgeDimensionService { base.height(), base.logicalHeight(), base.infiniburn(), - AgeEnvironmentCompiler.skyEffects(age.plan(), base.effectsLocation()), + age.plan() == null && sky.custom() + ? AgeEnvironmentCompiler.CUSTOM_SKY_EFFECTS + : AgeEnvironmentCompiler.skyEffects(age.plan(), base.effectsLocation()), ambientLight, base.monsterSettings() ); diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeEnvironmentCompiler.java b/src/main/java/net/dnorberg/rivencraft/book/AgeEnvironmentCompiler.java index 38ba1ed..82eb609 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeEnvironmentCompiler.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeEnvironmentCompiler.java @@ -2,10 +2,16 @@ package net.dnorberg.rivencraft.book; import net.dnorberg.rivencraft.writing.SymbolSemantics; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.dimension.BuiltinDimensionTypes; /** Resolves written environment pages without changing the seeded page-free Age path. */ public final class AgeEnvironmentCompiler { + public static final ResourceLocation CUSTOM_SKY_EFFECTS = ResourceLocation.fromNamespaceAndPath( + "rivencraft", "custom_sky"); private AgeEnvironmentCompiler() {} public static AgeTraits apply(AgeGenerationPlan plan, AgeTraits seeded) { @@ -19,6 +25,7 @@ public final class AgeEnvironmentCompiler { } else { applied = applyLegacy(plan, seeded); } + AgeSkyProfile.compile(plan, applied); return forTerrain(applied, plan.runtimeProfile()); } @@ -67,8 +74,7 @@ public final class AgeEnvironmentCompiler { 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"); + case ANGLE_MODIFIER -> { } // Consumed by the next independently rendered celestial. default -> { } } } @@ -81,6 +87,8 @@ public final class AgeEnvironmentCompiler { /** 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; + if (skyColor(plan) != -1 || staticSky(plan) || AgeSkyProfile.hasCustomCelestials(plan)) + return CUSTOM_SKY_EFFECTS; return plan.activeSymbols().stream() .filter(symbol -> symbol.semantics().role() == SymbolSemantics.Role.SKY) .map(symbol -> ((SymbolSemantics.ChoiceValue) symbol.semantics().value()).id()) @@ -92,6 +100,52 @@ public final class AgeEnvironmentCompiler { }).findFirst().orElse(fallback); } + public static boolean staticSky(AgeGenerationPlan plan) { + return plan != null && plan.activeSymbols().stream().anyMatch(symbol -> + symbol.semantics().role() == SymbolSemantics.Role.SKY + && ((SymbolSemantics.ChoiceValue) symbol.semantics().value()).id().equals("static")); + } + + /** An authored color is fixed; -1 means the biome or selected vanilla sky supplies it. */ + public static int skyColor(AgeGenerationPlan plan) { + if (plan == null) return -1; + return AgeSkyProfile.mixColors(plan.activeSymbols().stream() + .filter(symbol -> symbol.semantics().role() == SymbolSemantics.Role.SKY_COLOR) + .map(symbol -> ((SymbolSemantics.ChoiceValue) symbol.semantics().value()).id()) + .map(AgeSkyProfile::color).toList()); + } + + public static int selectedSkyColor(AgeGenerationPlan plan, ResourceLocation fallback, int biomeSkyColor) { + int authored = skyColor(plan); + if (authored != -1) return authored; + if (staticSky(plan)) return 0xFF929292; + ResourceLocation effects = skyEffects(plan, fallback); + return previewSkyColor(effects, biomeSkyColor); + } + + /** Shared server answer for book previews and the fallback crystal portal surface. */ + public static int previewSkyColor(ServerLevel level, BlockPos origin) { + int biomeColor = level.getBiome(origin).value().getSkyColor(); + var age = AgeRegistrySavedData.get(level).definitions().stream() + .filter(candidate -> AgeDimensionService.dimensionKey(candidate).equals(level.dimension())) + .findFirst(); + if (age.isEmpty()) return previewSkyColor(level.dimensionType().effectsLocation(), biomeColor); + var definition = age.orElseThrow(); + AgeSkyProfile sky = AgeSkyProfile.forAge(definition); + if (definition.plan() == null && definition.generationVersion() >= AgeSkyProfile.RANDOM_SKY_GENERATION_VERSION) { + if (sky.color() != -1) return sky.color(); + if (sky.style() == AgeSkyProfile.Style.STATIC) return 0xFF929292; + } + var profile = definition.plan() == null + ? AgeRuntimeProfiles.resolve(definition.generationProfile()) + : definition.plan().runtimeProfile(); + ResourceLocation baseEffects = level.getServer().registryAccess() + .lookupOrThrow(Registries.DIMENSION_TYPE) + .getOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, profile.dimensionType())) + .value().effectsLocation(); + return selectedSkyColor(definition.plan(), baseEffects, biomeColor); + } + public static boolean hasWrittenPhase(AgeGenerationPlan plan) { if (plan == null) return false; if (plan.version() >= 4) return orderedCelestial(plan, null).writtenPhase(); diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeGenerationPlan.java b/src/main/java/net/dnorberg/rivencraft/book/AgeGenerationPlan.java index b86f384..d16e993 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeGenerationPlan.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeGenerationPlan.java @@ -188,7 +188,7 @@ public record AgeGenerationPlan(int version, long ageSeed, OrderedWriting writin String raw = item.getString("Value"); SymbolSemantics.Value value = switch (role) { case TERRAIN, BIOME, BLOCK, FEATURE -> new SymbolSemantics.ResourceValue(ResourceLocation.parse(raw)); - case PERIOD_MODIFIER, ANGLE_MODIFIER, PHASE_MODIFIER -> + case PERIOD_MODIFIER, ANGLE_MODIFIER, PHASE_MODIFIER, SIZE_MODIFIER -> new SymbolSemantics.NumberValue(Double.parseDouble(raw)); default -> new SymbolSemantics.ChoiceValue(raw); }; diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeInstability.java b/src/main/java/net/dnorberg/rivencraft/book/AgeInstability.java index 217efe7..0db5d4d 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeInstability.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeInstability.java @@ -51,6 +51,10 @@ public record AgeInstability(int version, int score, List breakdow } static AgeInstability compute(AgeGenerationPlan plan, AgeTraits traits, RegistryAccess registries) { + return compute(plan, traits, registries, Long.MIN_VALUE); + } + + static AgeInstability compute(AgeGenerationPlan plan, AgeTraits traits, RegistryAccess registries, long ageSeed) { Objects.requireNonNull(traits, "traits"); List contributions = new ArrayList<>(); if (plan == null) { @@ -82,6 +86,17 @@ 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) + : plan == null ? new AgeSkyProfile(AgeSkyProfile.Style.INHERITED, -1, List.of()) + : AgeSkyProfile.compile(plan, traits); + int seenSuns = 0; + for (AgeSkyProfile.Body body : sky.bodies()) { + if (body.kind() != AgeSkyProfile.Kind.SUN) continue; + if (++seenSuns <= 2) continue; + int points = 1000 * (seenSuns - 2); + contributions.add(new Contribution("extra_sun", -1, points, + "Sun " + seenSuns + " adds growing instability")); + } return new AgeInstability(CURRENT_VERSION, contributions.stream().mapToInt(Contribution::points).sum(), contributions); } @@ -94,7 +109,8 @@ public record AgeInstability(int version, int score, List breakdow if (cave) { var role = symbol.semantics().role(); if (switch (role) { - case WEATHER, SEASON, CELESTIAL, PERIOD_MODIFIER, PHASE_MODIFIER, ANGLE_MODIFIER -> true; + case WEATHER, SEASON, CELESTIAL, PERIOD_MODIFIER, PHASE_MODIFIER, + ANGLE_MODIFIER, DIRECTION_MODIFIER -> true; default -> false; }) { contributions.add(new Contribution("cave_ignored_" + role.id(), symbol.index(), diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeRegistrySavedData.java b/src/main/java/net/dnorberg/rivencraft/book/AgeRegistrySavedData.java index ce22212..1c99910 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/AgeRegistrySavedData.java +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeRegistrySavedData.java @@ -179,7 +179,7 @@ public final class AgeRegistrySavedData extends SavedData { AgeTraits effectiveTraits = plan == null ? AgeEnvironmentCompiler.forTerrain(traits, AgeRuntimeProfiles.resolve(seedProfile)) : AgeEnvironmentCompiler.apply(plan, traits); - AgeInstability instability = AgeInstability.compute(plan, effectiveTraits, registries); + AgeInstability instability = AgeInstability.compute(plan, effectiveTraits, registries, seed); AgeInstabilityPlan instabilityPlan = AgeInstabilityPlan.select(instability, seed); AgeDefinition created = new AgeDefinition( reference.id(), diff --git a/src/main/java/net/dnorberg/rivencraft/book/AgeSkyProfile.java b/src/main/java/net/dnorberg/rivencraft/book/AgeSkyProfile.java new file mode 100644 index 0000000..56b896f --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/book/AgeSkyProfile.java @@ -0,0 +1,181 @@ +package net.dnorberg.rivencraft.book; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; +import net.dnorberg.rivencraft.writing.SymbolSemantics; + +/** The ordered, deterministic sky specification for an Age. No client data is trusted here. */ +public record AgeSkyProfile(Style style, int color, List bodies) { + public static final int RANDOM_SKY_GENERATION_VERSION = 17; + private static final int[] COLORS = {0xB74A50, 0xD9834B, 0xDBC574, 0x5AAB80, + 0x6FA8DF, 0x9870B9, 0xD5DBE1, 0x252531}; + + public AgeSkyProfile { + bodies = List.copyOf(bodies); + long suns = bodies.stream().filter(body -> body.kind() == Kind.SUN).count(); + long moons = bodies.size() - suns; + if (suns > 10 || moons > 10) throw new IllegalArgumentException("An Age may have at most ten suns and ten moons"); + } + + 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 new AgeSkyProfile(Style.INHERITED, -1, List.of()); + } + + public static AgeSkyProfile random(long seed, AgeTraits traits) { + Random random = new Random(seed ^ 0x534B5950524F464CL); + Style style = Style.values()[random.nextInt(Style.values().length)]; + int color = randomColor(random); + int suns = random.nextInt(4); + int moons = random.nextInt(4); + List bodies = new ArrayList<>(); + for (int i = 0; i < suns; i++) + bodies.add(new Body(Kind.SUN, randomColor(random), + 0.75F + random.nextFloat(), random.nextFloat() * 360.0F, + traits.celestialPeriod(), random.nextFloat(), random.nextBoolean())); + for (int i = 0; i < moons; i++) + bodies.add(new Body(Kind.MOON, randomColor(random), + 0.75F + random.nextFloat(), random.nextFloat() * 360.0F, + traits.celestialPeriod(), random.nextFloat(), random.nextBoolean())); + return new AgeSkyProfile(style, color, bodies); + } + + public static AgeSkyProfile compile(AgeGenerationPlan plan, AgeTraits traits) { + if (plan == null) return new AgeSkyProfile(Style.INHERITED, -1, List.of()); + Style style = Style.INHERITED; + int color = AgeEnvironmentCompiler.skyColor(plan); + boolean customBodies = hasCustomCelestials(plan); + List bodies = new ArrayList<>(); + Set active = new HashSet<>(plan.activeSymbolIndices()); + AgeTraits seeded = AgeTraits.select(plan.ageSeed()); + double period = 1.0D; + float angle = seeded.celestialAngle(); + float phase = seeded.celestialPhase(); + float size = 1.0F; + List bodyColors = new ArrayList<>(); + boolean clockwise = true; + for (AgeGenerationPlan.ResolvedSymbol symbol : plan.symbols()) { + if (!active.contains(symbol.index())) continue; + SymbolSemantics semantics = symbol.semantics(); + switch (semantics.role()) { + case SKY -> style = switch (((SymbolSemantics.ChoiceValue) semantics.value()).id()) { + case "overworld" -> Style.OVERWORLD; + case "nether" -> Style.NETHER; + case "end" -> Style.END; + case "static" -> Style.STATIC; + default -> throw new IllegalArgumentException("Unknown sky style"); + }; + case PERIOD_MODIFIER -> period = ((SymbolSemantics.NumberValue) semantics.value()).amount(); + case ANGLE_MODIFIER -> angle = (float) ((SymbolSemantics.NumberValue) semantics.value()).amount(); + case PHASE_MODIFIER -> phase = (float) (((SymbolSemantics.NumberValue) semantics.value()).amount() / 360.0D); + case SIZE_MODIFIER -> size = (float) ((SymbolSemantics.NumberValue) semantics.value()).amount(); + case CELESTIAL_COLOR -> bodyColors.add(color( + ((SymbolSemantics.ChoiceValue) semantics.value()).id())); + case DIRECTION_MODIFIER -> clockwise = ((SymbolSemantics.ChoiceValue) semantics.value()) + .id().equals("clockwise"); + case CELESTIAL -> { + String choice = ((SymbolSemantics.ChoiceValue) semantics.value()).id(); + int bodyColor = bodyColors.isEmpty() ? 0xFFFFFF : mixColors(bodyColors); + if (choice.equals("normal_time")) { + if (customBodies) { + bodies.add(new Body(Kind.SUN, bodyColor, size, angle, period, phase, clockwise)); + bodies.add(new Body(Kind.MOON, bodyColor, 1.0F, angle, period, + (phase + 0.5F) % 1.0F, clockwise)); + } + } else { + bodies.add(new Body(choice.equals("sun") ? Kind.SUN : Kind.MOON, + bodyColor, size, angle, period, phase, clockwise)); + } + period = 1.0D; + angle = seeded.celestialAngle(); + phase = seeded.celestialPhase(); + size = 1.0F; + bodyColors.clear(); + clockwise = true; + } + default -> { } + } + } + // Missing branches are seed-completed, including the celestial branch. + // An explicit sky or sky-color page does not suppress that completion. + if (plan.activeSymbols().stream().noneMatch(symbol -> + symbol.semantics().role() == SymbolSemantics.Role.CELESTIAL)) { + bodies.addAll(random(plan.ageSeed(), seeded).bodies()); + } + return new AgeSkyProfile(style, color, bodies); + } + + public static int mixColors(List colors) { + if (colors.isEmpty()) return -1; + long red = 0, green = 0, blue = 0; + for (int color : colors) { + red += color >> 16 & 255; + green += color >> 8 & 255; + blue += color & 255; + } + int count = colors.size(); + return (int) ((red + count / 2) / count) << 16 + | (int) ((green + count / 2) / count) << 8 + | (int) ((blue + count / 2) / count); + } + + private static int randomColor(Random random) { + int count = 1 + random.nextInt(3); + List colors = new ArrayList<>(count); + for (int i = 0; i < count; i++) colors.add(COLORS[random.nextInt(COLORS.length)]); + return mixColors(colors); + } + + public static boolean hasCustomCelestials(AgeGenerationPlan plan) { + return plan != null && plan.activeSymbols().stream().anyMatch(symbol -> + symbol.semantics().role() == SymbolSemantics.Role.CELESTIAL + && !((SymbolSemantics.ChoiceValue) symbol.semantics().value()).id().equals("normal_time") + || symbol.semantics().role() == SymbolSemantics.Role.CELESTIAL_COLOR + || symbol.semantics().role() == SymbolSemantics.Role.SIZE_MODIFIER + || symbol.semantics().role() == SymbolSemantics.Role.DIRECTION_MODIFIER); + } + + public boolean custom() { + return style == Style.STATIC || color != -1 || !bodies.isEmpty(); + } + + /** Vanilla has one skylight channel; combine visible bodies into that channel. */ + public float lightAt(double dayTime) { + float sunlight = 0.0F; + float moonlight = 0.0F; + for (Body body : bodies) { + double orbit = body.period() == 0.0D ? body.phase() + : (body.clockwise() ? 1.0D : -1.0D) * dayTime + / (24000.0D * body.period()) + body.phase(); + float altitude = (float) Math.cos(orbit * Math.PI * 2.0D); + if (altitude <= 0.0F) continue; + if (body.kind() == Kind.SUN) sunlight += altitude; + else moonlight += altitude * 0.18F; + } + return Math.min(1.0F, sunlight > 0.0F ? sunlight : moonlight); + } + + public static int color(String name) { + return switch (name) { + case "red" -> COLORS[0]; + case "orange" -> COLORS[1]; + case "yellow" -> COLORS[2]; + case "green" -> COLORS[3]; + case "blue" -> COLORS[4]; + case "purple" -> COLORS[5]; + case "white" -> COLORS[6]; + case "black" -> COLORS[7]; + default -> throw new IllegalArgumentException("Unknown sky color " + name); + }; + } + + public enum Style { INHERITED, OVERWORLD, NETHER, END, STATIC } + public enum Kind { SUN, MOON } + public record Body(Kind kind, int color, float size, float angle, + double period, float phase, boolean clockwise) { } +} diff --git a/src/main/java/net/dnorberg/rivencraft/book/EnvironmentWritingGameTests.java b/src/main/java/net/dnorberg/rivencraft/book/EnvironmentWritingGameTests.java index fabe366..3fa92d2 100644 --- a/src/main/java/net/dnorberg/rivencraft/book/EnvironmentWritingGameTests.java +++ b/src/main/java/net/dnorberg/rivencraft/book/EnvironmentWritingGameTests.java @@ -52,9 +52,11 @@ public final class EnvironmentWritingGameTests { public static void environmentPagesEnterCreativeAndJeiFeed(GameTestHelper helper) { Set expected = Set.of( "lighting/normal", "lighting/dark", "lighting/bright", - "sky/overworld", "sky/nether", "sky/end", + "sky/overworld", "sky/nether", "sky/end", "sky/static", "sky_color/purple", "weather/normal", "weather/fast", "weather/slow", "weather/off", - "weather/always", "weather/storm", "celestial/normal_time", + "weather/always", "weather/storm", "celestial/normal_time", "celestial/sun", + "celestial/moon", "celestial_color/red", "size/large", "angle/east", + "direction/clockwise", "direction/counterclockwise", "season/no_seasons", "season/seasons", "season/eternal_winter", "season/eternal_summer", "season/eternal_fall", "season/eternal_spring", "period/zero", "period/half", "period/full", "period/double", @@ -135,6 +137,104 @@ public final class EnvironmentWritingGameTests { helper.succeed(); } + @GameTest(template = "empty") + public static void authoredStaticSkyColorSurvivesWritingAndFeedsPreview(GameTestHelper helper) { + OrderedWriting writing = new OrderedWriting(List.of( + WritingEntry.linkPanel(), page("terrain/standard"), page("sky/static"), + page("sky_color/purple"), page("celestial/normal_time"))); + AgeTraits seeded = AgeTraits.select(44L); + AgeGenerationPlan plan = AgeGenerationPlan.compile(writing, 44L, AgeGenerationProfiles.STANDARD, + seeded, helper.getLevel().registryAccess()); + helper.assertTrue(AgeEnvironmentCompiler.staticSky(plan), + "The authored Static Sky page must survive Age compilation"); + helper.assertValueEqual(AgeEnvironmentCompiler.skyEffects(plan, BuiltinDimensionTypes.OVERWORLD_EFFECTS), + AgeEnvironmentCompiler.CUSTOM_SKY_EFFECTS, + "The static sky must select Rivencraft's custom renderer"); + helper.assertValueEqual(AgeEnvironmentCompiler.selectedSkyColor(plan, + BuiltinDimensionTypes.OVERWORLD_EFFECTS, 0xFF77AAFF), + 0x9870B9, "The authored color must tint the sky and preview consistently"); + AgeGenerationPlan restored = AgeGenerationPlan.load(plan.save()); + helper.assertTrue(AgeEnvironmentCompiler.staticSky(restored), + "A saved Age must retain its static sky choice"); + helper.assertValueEqual(AgeEnvironmentCompiler.skyColor(restored), 0x9870B9, + "A saved Age must retain its sky color choice"); + AgeGenerationPlan coloredEnd = AgeGenerationPlan.compile(new OrderedWriting(List.of( + WritingEntry.linkPanel(), page("terrain/standard"), page("sky/end"), + page("sky_color/red"), page("celestial/normal_time"))), + 45L, AgeGenerationProfiles.STANDARD, AgeTraits.select(45L), + helper.getLevel().registryAccess()); + helper.assertValueEqual(AgeEnvironmentCompiler.skyEffects(coloredEnd, + BuiltinDimensionTypes.OVERWORLD_EFFECTS), + AgeEnvironmentCompiler.CUSTOM_SKY_EFFECTS, + "A color page must make non-Overworld skies renderable by the custom sky effect"); + helper.assertValueEqual(AgeEnvironmentCompiler.selectedSkyColor(coloredEnd, + BuiltinDimensionTypes.OVERWORLD_EFFECTS, 0xFF77AAFF), + 0xB74A50, "A color page must override the selected sky type's default tint"); + helper.assertTrue(PageItem.bundledCreativeSymbols().contains("sky/static") + && PageItem.bundledCreativeSymbols().contains("sky_color/purple"), + "New sky pages must be available through the Creative and JEI page catalog"); + helper.succeed(); + } + + @GameTest(template = "empty") + public static void skyAndCelestialColorsBlendWithoutRequiringBodies(GameTestHelper helper) { + OrderedWriting writing = new OrderedWriting(List.of( + WritingEntry.linkPanel(), page("terrain/standard"), page("sky/overworld"), + page("sky_color/red"), page("sky_color/blue"), + page("celestial_color/yellow"), page("celestial_color/blue"), page("celestial/sun"))); + helper.assertTrue(WritingGrammar.parse(writing).canCreate(), + "Two sky colors and two colors for one sun must be valid writing"); + AgeTraits traits = AgeTraits.select(46L); + AgeGenerationPlan plan = AgeGenerationPlan.compile(writing, 46L, + AgeGenerationProfiles.STANDARD, traits, helper.getLevel().registryAccess()); + int skyColor = AgeSkyProfile.mixColors(List.of( + AgeSkyProfile.color("red"), AgeSkyProfile.color("blue"))); + int sunColor = AgeSkyProfile.mixColors(List.of( + AgeSkyProfile.color("yellow"), AgeSkyProfile.color("blue"))); + helper.assertValueEqual(AgeEnvironmentCompiler.skyColor(plan), skyColor, + "Sky color pages must blend without discarding either page"); + AgeSkyProfile profile = AgeSkyProfile.compile(plan, traits); + helper.assertValueEqual(profile.bodies().size(), 1, "The written Age has exactly one sun"); + helper.assertValueEqual(profile.bodies().getFirst().color(), sunColor, + "Celestial colors must blend for the following body"); + helper.assertValueEqual(AgeSkyProfile.compile(AgeGenerationPlan.load(plan.save()), traits), profile, + "Blended colors must survive the saved writing plan"); + + AgeSkyProfile bodyless = new AgeSkyProfile(AgeSkyProfile.Style.OVERWORLD, skyColor, List.of()); + helper.assertTrue(bodyless.custom() && bodyless.bodies().isEmpty(), + "An Age can have an authored sky without any celestial bodies"); + helper.succeed(); + } + + @GameTest(template = "empty") + public static void randomAndAuthoredCelestialsKeepIndependentOrbits(GameTestHelper helper) { + AgeTraits traits = AgeTraits.select(12345L); + AgeSkyProfile first = AgeSkyProfile.random(12345L, traits); + helper.assertValueEqual(first, AgeSkyProfile.random(12345L, traits), + "A random Age must recreate the same sky from its saved seed"); + helper.assertTrue(first.bodies().stream().filter(body -> body.kind() == AgeSkyProfile.Kind.SUN).count() <= 10 + && first.bodies().stream().filter(body -> body.kind() == AgeSkyProfile.Kind.MOON).count() <= 10, + "Random Ages must obey the same celestial limits as authored Ages"); + OrderedWriting writing = new OrderedWriting(List.of( + WritingEntry.linkPanel(), page("terrain/standard"), page("sky/end"), + page("angle/east"), page("direction/counterclockwise"), page("celestial_color/red"), + page("size/large"), page("period/half"), page("celestial/sun"), + page("direction/clockwise"), page("celestial/moon"))); + AgeGenerationPlan plan = AgeGenerationPlan.compile(writing, 12345L, AgeGenerationProfiles.STANDARD, + traits, helper.getLevel().registryAccess()); + AgeSkyProfile sky = AgeSkyProfile.compile(plan, AgeEnvironmentCompiler.apply(plan, traits)); + helper.assertValueEqual(sky.bodies().size(), 2, "Both written celestial bodies must be retained"); + helper.assertValueEqual(sky.bodies().getFirst().angle(), 90.0F, + "The first body's compass angle must be independent"); + helper.assertFalse(sky.bodies().getFirst().clockwise(), + "The first body should move counterclockwise"); + helper.assertTrue(sky.bodies().getLast().clockwise(), + "The second body should move clockwise"); + helper.assertValueEqual(AgeSkyProfile.compile(AgeGenerationPlan.load(plan.save()), traits), sky, + "Body angle and direction must survive Age-plan serialization"); + helper.succeed(); + } + @GameTest(template = "empty") public static void writtenEnvironmentPersistsAndPageFreeAgesStaySeeded(GameTestHelper helper) { OrderedWriting writing = new OrderedWriting(List.of( diff --git a/src/main/java/net/dnorberg/rivencraft/client/ClientAgeCelestialAngles.java b/src/main/java/net/dnorberg/rivencraft/client/ClientAgeCelestialAngles.java index 0ed47c8..19090a8 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/ClientAgeCelestialAngles.java +++ b/src/main/java/net/dnorberg/rivencraft/client/ClientAgeCelestialAngles.java @@ -2,23 +2,58 @@ package net.dnorberg.rivencraft.client; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import net.dnorberg.rivencraft.book.AgeSkyProfile; import net.dnorberg.rivencraft.network.AgeCelestialAnglePayload; import net.minecraft.resources.ResourceKey; import net.minecraft.world.level.Level; import net.neoforged.neoforge.network.handling.IPayloadContext; +import net.neoforged.neoforge.network.PacketDistributor; +import net.dnorberg.rivencraft.network.RequestAgeSkyPayload; /** Client cache used by both the active world and remote preview renderers. */ public final class ClientAgeCelestialAngles { private static final Map, Float> ANGLES = new ConcurrentHashMap<>(); + private static final Map, AgeSkyProfile> SKIES = new ConcurrentHashMap<>(); + private static final Map, Long> LAST_REQUEST = new ConcurrentHashMap<>(); private ClientAgeCelestialAngles() { } public static void handle(AgeCelestialAnglePayload payload, IPayloadContext context) { ANGLES.put(payload.dimension(), payload.angle()); + SKIES.put(payload.dimension(), payload.sky()); + LAST_REQUEST.remove(payload.dimension()); + } + + public static void requestIfMissing(ResourceKey dimension) { + if (!dimension.location().getNamespace().equals("rivencraft") || SKIES.containsKey(dimension)) return; + long now = System.nanoTime(); + Long previous = LAST_REQUEST.putIfAbsent(dimension, now); + if (previous != null && now - previous < 2_000_000_000L) return; + LAST_REQUEST.put(dimension, now); + PacketDistributor.sendToServer(new RequestAgeSkyPayload(dimension)); + } + + public static void clear() { + ANGLES.clear(); + SKIES.clear(); + LAST_REQUEST.clear(); } public static float get(ResourceKey dimension) { return ANGLES.getOrDefault(dimension, 0.0F); } + + public static int skyColor(ResourceKey dimension) { + return sky(dimension).color(); + } + + public static boolean staticSky(ResourceKey dimension) { + return sky(dimension).style() == AgeSkyProfile.Style.STATIC; + } + + public static AgeSkyProfile sky(ResourceKey dimension) { + return SKIES.getOrDefault(dimension, + new AgeSkyProfile(AgeSkyProfile.Style.INHERITED, -1, java.util.List.of())); + } } diff --git a/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java b/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java index 94d4b99..ce1945c 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java +++ b/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java @@ -19,6 +19,12 @@ import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.client.event.EntityRenderersEvent; import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent; import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; +import net.neoforged.neoforge.client.event.RegisterDimensionSpecialEffectsEvent; +import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent; +import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; +import net.dnorberg.rivencraft.block.entity.CrystalPortalBlockEntity; +import net.dnorberg.rivencraft.block.entity.BookReceptacleBlockEntity; +import net.dnorberg.rivencraft.book.AgeEnvironmentCompiler; import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions; import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent; import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; @@ -43,6 +49,23 @@ public final class ClientEvents { event.registerBlockEntityRenderer(ModBlockEntities.STAR_FISSURE.get(), StarFissureRenderer::new); } + @SubscribeEvent + public static void registerSkyEffects(RegisterDimensionSpecialEffectsEvent event) { + event.register(AgeEnvironmentCompiler.CUSTOM_SKY_EFFECTS, new RivencraftSkyEffects()); + } + + @SubscribeEvent + public static void registerPortalColor(RegisterColorHandlersEvent.Block event) { + event.register((state, level, pos, tintIndex) -> { + if (level == null || pos == null || tintIndex != 0 + || !(level.getBlockEntity(pos) instanceof CrystalPortalBlockEntity portal) + || portal.receptacle() == null + || !(level.getBlockEntity(portal.receptacle()) instanceof BookReceptacleBlockEntity holder) + || holder.destination() == null) return 0xFFFFFF; + return holder.skyColor(); + }, ModBlocks.CRYSTAL_PORTAL.get()); + } + @SubscribeEvent public static void registerScreens(RegisterMenuScreensEvent event) { event.register(ModMenus.NOTEBOOK.get(), NotebookScreen::new); @@ -99,6 +122,11 @@ public final class ClientEvents { NeoForge.EVENT_BUS.addListener(ClientBookExploreOverlay::onClientTickPost); NeoForge.EVENT_BUS.addListener(ClientBookExploreOverlay::onRenderGui); NeoForge.EVENT_BUS.addListener(ClientBookExploreOverlay::onMouseButton); + NeoForge.EVENT_BUS.addListener(ClientEvents::onLogout); + } + + private static void onLogout(ClientPlayerNetworkEvent.LoggingOut event) { + ClientAgeCelestialAngles.clear(); } } diff --git a/src/main/java/net/dnorberg/rivencraft/client/RivencraftSkyEffects.java b/src/main/java/net/dnorberg/rivencraft/client/RivencraftSkyEffects.java new file mode 100644 index 0000000..3fb3b70 --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/client/RivencraftSkyEffects.java @@ -0,0 +1,145 @@ +package net.dnorberg.rivencraft.client; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.BufferUploader; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexFormat; +import java.util.Random; +import net.dnorberg.rivencraft.book.AgeSkyProfile; +import net.minecraft.client.Camera; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.DimensionSpecialEffects; +import net.minecraft.client.renderer.FogRenderer; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.world.phys.Vec3; +import org.joml.Matrix4f; +import com.mojang.math.Axis; +import net.minecraft.resources.ResourceLocation; + +/** An authored sky can use vanilla celestial rendering or Legacy-style moving TV static. */ +public final class RivencraftSkyEffects extends DimensionSpecialEffects { + private static final int GRID = 16; + private static final float RADIUS = 100.0F; + private static final ResourceLocation SUN = ResourceLocation.withDefaultNamespace("textures/environment/sun.png"); + private static final ResourceLocation MOON = ResourceLocation.withDefaultNamespace("textures/environment/moon_phases.png"); + + public RivencraftSkyEffects() { + super(192.0F, true, SkyType.NORMAL, false, false); + } + + @Override + public Vec3 getBrightnessDependentFogColor(Vec3 color, float brightness) { + return color.multiply(brightness * 0.94F + 0.06F, + brightness * 0.94F + 0.06F, brightness * 0.91F + 0.09F); + } + + @Override + public boolean isFoggyAt(int x, int y) { + return false; + } + + @Override + public boolean renderSky(ClientLevel level, int ticks, float partialTick, Matrix4f modelViewMatrix, + Camera camera, Matrix4f projectionMatrix, boolean isFoggy, Runnable setupFog) { + AgeSkyProfile sky = ClientAgeCelestialAngles.sky(level.dimension()); + if (sky.style() != AgeSkyProfile.Style.STATIC && sky.color() == -1 && sky.bodies().isEmpty() + && sky.style() != AgeSkyProfile.Style.NETHER && sky.style() != AgeSkyProfile.Style.END) + return false; + setupFog.run(); + if (isFoggy) return true; + + PoseStack pose = new PoseStack(); + pose.mulPose(modelViewMatrix); + Matrix4f matrix = pose.last().pose(); + Random random = new Random((level.getGameTime() + (long) ticks) / 2L); + int tint = sky.color(); + if (tint == -1) tint = switch (sky.style()) { + case NETHER -> 0x6B2725; + case END -> 0x211835; + case STATIC -> 0x929292; + default -> level.getBiome(camera.getBlockPosition()).value().getSkyColor(); + }; + + RenderSystem.depthMask(false); + RenderSystem.disableCull(); + RenderSystem.setShader(GameRenderer::getPositionColorShader); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + FogRenderer.setupNoFog(); + BufferBuilder vertices = Tesselator.getInstance().begin( + VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); + for (int axis = 0; axis < 3; axis++) { + for (int sign : new int[]{-1, 1}) { + for (int x = 0; x < GRID; x++) { + for (int y = 0; y < GRID; y++) { + int noise = sky.style() == AgeSkyProfile.Style.STATIC + ? 65 + random.nextInt(160) : 255; + int red = ((tint >> 16) & 255) * noise / 255; + int green = ((tint >> 8) & 255) * noise / 255; + int blue = (tint & 255) * noise / 255; + int color = 0xFF000000 | red << 16 | green << 8 | blue; + float left = -RADIUS + x * (2 * RADIUS / GRID); + float right = -RADIUS + (x + 1) * (2 * RADIUS / GRID); + float bottom = -RADIUS + y * (2 * RADIUS / GRID); + float top = -RADIUS + (y + 1) * (2 * RADIUS / GRID); + vertex(vertices, matrix, axis, sign, left, bottom, color); + vertex(vertices, matrix, axis, sign, right, bottom, color); + vertex(vertices, matrix, axis, sign, right, top, color); + vertex(vertices, matrix, axis, sign, left, top, color); + } + } + } + } + BufferUploader.drawWithShader(vertices.buildOrThrow()); + if (!sky.bodies().isEmpty()) { + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + double time = level.getGameTime() + partialTick; + for (AgeSkyProfile.Body body : sky.bodies()) drawBody(pose, body, time); + RenderSystem.disableBlend(); + } + setupFog.run(); + RenderSystem.enableCull(); + RenderSystem.depthMask(true); + return true; + } + + private static void drawBody(PoseStack pose, AgeSkyProfile.Body body, double time) { + pose.pushPose(); + pose.mulPose(Axis.YP.rotationDegrees(body.angle())); + double orbit = body.period() == 0 ? body.phase() + : (body.clockwise() ? 1.0D : -1.0D) * time + / (24000.0D * body.period()) + body.phase(); + pose.mulPose(Axis.XP.rotationDegrees((float) (orbit * 360.0D))); + float size = (body.kind() == AgeSkyProfile.Kind.SUN ? 30.0F : 20.0F) * body.size(); + int color = body.color(); + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, body.kind() == AgeSkyProfile.Kind.SUN ? SUN : MOON); + RenderSystem.setShaderColor(((color >> 16) & 255) / 255.0F, + ((color >> 8) & 255) / 255.0F, (color & 255) / 255.0F, 1.0F); + float uMax = body.kind() == AgeSkyProfile.Kind.SUN ? 1.0F : 0.25F; + float vMax = body.kind() == AgeSkyProfile.Kind.SUN ? 1.0F : 0.5F; + BufferBuilder vertices = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, + DefaultVertexFormat.POSITION_TEX); + Matrix4f matrix = pose.last().pose(); + vertices.addVertex(matrix, -size, RADIUS - 1.0F, -size).setUv(0.0F, 0.0F); + vertices.addVertex(matrix, size, RADIUS - 1.0F, -size).setUv(uMax, 0.0F); + vertices.addVertex(matrix, size, RADIUS - 1.0F, size).setUv(uMax, vMax); + vertices.addVertex(matrix, -size, RADIUS - 1.0F, size).setUv(0.0F, vMax); + BufferUploader.drawWithShader(vertices.buildOrThrow()); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + pose.popPose(); + } + + private static void vertex(BufferBuilder vertices, Matrix4f matrix, int axis, int sign, + float x, float y, int color) { + float fixed = sign * RADIUS; + switch (axis) { + case 0 -> vertices.addVertex(matrix, fixed, x, y).setColor(color); + case 1 -> vertices.addVertex(matrix, x, fixed, y).setColor(color); + default -> vertices.addVertex(matrix, x, y, fixed).setColor(color); + } + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/event/AgeCelestialAngleSync.java b/src/main/java/net/dnorberg/rivencraft/event/AgeCelestialAngleSync.java index a89186d..8fda11d 100644 --- a/src/main/java/net/dnorberg/rivencraft/event/AgeCelestialAngleSync.java +++ b/src/main/java/net/dnorberg/rivencraft/event/AgeCelestialAngleSync.java @@ -3,11 +3,11 @@ package net.dnorberg.rivencraft.event; import net.dnorberg.rivencraft.book.AgeDefinition; import net.dnorberg.rivencraft.book.AgeDimensionService; import net.dnorberg.rivencraft.book.AgeRegistrySavedData; +import net.dnorberg.rivencraft.book.AgeSkyProfile; import net.dnorberg.rivencraft.network.AgeCelestialAnglePayload; import net.minecraft.server.level.ServerPlayer; import net.neoforged.neoforge.event.entity.player.PlayerEvent; import net.neoforged.neoforge.network.PacketDistributor; -import net.neoforged.neoforge.network.registration.NetworkRegistry; /** Sends saved celestial angles when a client joins or enters an Age. */ public final class AgeCelestialAngleSync { @@ -34,10 +34,8 @@ public final class AgeCelestialAngleSync { } public static void send(ServerPlayer player, AgeDefinition age) { - if (!NetworkRegistry.hasChannel(player.connection, AgeCelestialAnglePayload.TYPE.id())) { - return; - } PacketDistributor.sendToPlayer(player, new AgeCelestialAnglePayload( - AgeDimensionService.dimensionKey(age), age.traits().celestialAngle())); + AgeDimensionService.dimensionKey(age), age.traits().celestialAngle(), + AgeSkyProfile.forAge(age))); } } diff --git a/src/main/java/net/dnorberg/rivencraft/mixin/LevelSkyBrightnessMixin.java b/src/main/java/net/dnorberg/rivencraft/mixin/LevelSkyBrightnessMixin.java new file mode 100644 index 0000000..87f39ef --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/mixin/LevelSkyBrightnessMixin.java @@ -0,0 +1,40 @@ +package net.dnorberg.rivencraft.mixin; + +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.book.AgeDimensionService; +import net.dnorberg.rivencraft.book.AgeRegistrySavedData; +import net.dnorberg.rivencraft.book.AgeSkyProfile; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** Combines multiple celestial light sources into Minecraft's one skylight channel. */ +@Mixin(Level.class) +public abstract class LevelSkyBrightnessMixin { + @Shadow private int skyDarken; + + @Inject(method = "updateSkyBrightness", at = @At("TAIL")) + private void rivencraft$stackCelestialLight(CallbackInfo callback) { + if (!((Object) this instanceof ServerLevel level) || !level.dimensionType().hasSkyLight()) return; + // ServerLevel updates sky brightness in its constructor, before the + // MinecraftServer has registered even the Overworld level. Only an + // established Rivencraft Age can have a saved celestial profile. + if (!level.dimension().location().getNamespace().equals(Rivencraft.MOD_ID) + || !level.dimension().location().getPath().startsWith("age/") + || level.getServer().overworld() == null) return; + AgeRegistrySavedData.get(level).definitions().stream() + .filter(age -> AgeDimensionService.dimensionKey(age).equals(level.dimension())) + .findFirst().ifPresent(age -> { + AgeSkyProfile sky = AgeSkyProfile.forAge(age); + if (sky.bodies().isEmpty()) return; + float light = sky.lightAt(level.getDayTime()); + float weather = (1.0F - level.getRainLevel(1.0F) * 5.0F / 16.0F) + * (1.0F - level.getThunderLevel(1.0F) * 5.0F / 16.0F); + skyDarken = (int) ((1.0F - light * weather) * 11.0F); + }); + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/mixin/client/ClientLevelSkyColorMixin.java b/src/main/java/net/dnorberg/rivencraft/mixin/client/ClientLevelSkyColorMixin.java new file mode 100644 index 0000000..f7963e9 --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/mixin/client/ClientLevelSkyColorMixin.java @@ -0,0 +1,40 @@ +package net.dnorberg.rivencraft.mixin.client; + +import net.dnorberg.rivencraft.client.ClientAgeCelestialAngles; +import net.dnorberg.rivencraft.book.AgeSkyProfile; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.world.phys.Vec3; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** Keeps authored color pages visible in the sky without replacing vanilla sun and stars. */ +@Mixin(ClientLevel.class) +public abstract class ClientLevelSkyColorMixin { + @Inject(method = "getSkyColor", at = @At("RETURN"), cancellable = true) + private void rivencraft$colorAuthoredSky(Vec3 position, float partialTick, + CallbackInfoReturnable result) { + ClientLevel level = (ClientLevel) (Object) this; + int color = ClientAgeCelestialAngles.skyColor(level.dimension()); + if (color == -1 || ClientAgeCelestialAngles.staticSky(level.dimension())) return; + Vec3 natural = result.getReturnValue(); + // Retain the vanilla day/night and precipitation darkening while replacing its hue. + AgeSkyProfile sky = ClientAgeCelestialAngles.sky(level.dimension()); + double brightness = sky.bodies().isEmpty() + ? Math.max(natural.x, Math.max(natural.y, natural.z)) + : Math.max(0.08D, sky.lightAt(level.getDayTime() + partialTick)); + result.setReturnValue(Vec3.fromRGB24(color).scale(brightness)); + } + + @Inject(method = "getSkyDarken", at = @At("RETURN"), cancellable = true) + private void rivencraft$stackClientCelestialLight(float partialTick, + CallbackInfoReturnable result) { + ClientLevel level = (ClientLevel) (Object) this; + AgeSkyProfile sky = ClientAgeCelestialAngles.sky(level.dimension()); + if (sky.bodies().isEmpty() || !level.dimensionType().hasSkyLight()) return; + float weather = (1.0F - level.getRainLevel(partialTick) * 5.0F / 16.0F) + * (1.0F - level.getThunderLevel(partialTick) * 5.0F / 16.0F); + result.setReturnValue(0.2F + 0.8F * sky.lightAt(level.getDayTime() + partialTick) * weather); + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/mixin/client/LevelRendererMixin.java b/src/main/java/net/dnorberg/rivencraft/mixin/client/LevelRendererMixin.java index 13ab2f6..47912bb 100644 --- a/src/main/java/net/dnorberg/rivencraft/mixin/client/LevelRendererMixin.java +++ b/src/main/java/net/dnorberg/rivencraft/mixin/client/LevelRendererMixin.java @@ -2,20 +2,37 @@ package net.dnorberg.rivencraft.mixin.client; import com.mojang.math.Axis; import net.dnorberg.rivencraft.client.ClientAgeCelestialAngles; +import net.dnorberg.rivencraft.client.RivencraftSkyEffects; +import net.minecraft.client.Camera; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.renderer.LevelRenderer; +import org.joml.Matrix4f; import org.joml.Quaternionf; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; /** Rotates the vanilla sun, moon, and star path by each Age's saved angle. */ @Mixin(LevelRenderer.class) public abstract class LevelRendererMixin { + private static final RivencraftSkyEffects RIVENCRAFT_SKY = new RivencraftSkyEffects(); + @Shadow private ClientLevel level; + @Inject(method = "renderSky", at = @At("HEAD"), cancellable = true) + private void rivencraft$renderAgeSky(Matrix4f frustumMatrix, Matrix4f projectionMatrix, + float partialTick, Camera camera, boolean isFoggy, + Runnable skyFogSetup, CallbackInfo callback) { + if (!level.dimension().location().getNamespace().equals("rivencraft")) return; + ClientAgeCelestialAngles.requestIfMissing(level.dimension()); + if (RIVENCRAFT_SKY.renderSky(level, (int) level.getGameTime(), partialTick, + frustumMatrix, camera, projectionMatrix, isFoggy, skyFogSetup)) callback.cancel(); + } + @Redirect( method = "renderSky", at = @At( diff --git a/src/main/java/net/dnorberg/rivencraft/network/AgeCelestialAnglePayload.java b/src/main/java/net/dnorberg/rivencraft/network/AgeCelestialAnglePayload.java index 09908ed..2266f90 100644 --- a/src/main/java/net/dnorberg/rivencraft/network/AgeCelestialAnglePayload.java +++ b/src/main/java/net/dnorberg/rivencraft/network/AgeCelestialAnglePayload.java @@ -1,6 +1,7 @@ package net.dnorberg.rivencraft.network; import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.book.AgeSkyProfile; import net.minecraft.core.registries.Registries; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.codec.StreamCodec; @@ -9,8 +10,8 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; -/** Synchronizes an Age's saved celestial-plane rotation to clients. */ -public record AgeCelestialAnglePayload(ResourceKey dimension, float angle) +/** Synchronizes an Age's saved celestial-plane rotation and authored sky to clients. */ +public record AgeCelestialAnglePayload(ResourceKey dimension, float angle, AgeSkyProfile sky) implements CustomPacketPayload { public static final Type TYPE = new Type<>( ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "age_celestial_angle") @@ -20,9 +21,34 @@ public record AgeCelestialAnglePayload(ResourceKey dimension, float angle (buffer, payload) -> { buffer.writeResourceKey(payload.dimension()); buffer.writeFloat(payload.angle()); + buffer.writeEnum(payload.sky().style()); + buffer.writeInt(payload.sky().color()); + buffer.writeVarInt(payload.sky().bodies().size()); + for (AgeSkyProfile.Body body : payload.sky().bodies()) { + buffer.writeEnum(body.kind()); + buffer.writeInt(body.color()); + buffer.writeFloat(body.size()); + buffer.writeFloat(body.angle()); + buffer.writeDouble(body.period()); + buffer.writeFloat(body.phase()); + buffer.writeBoolean(body.clockwise()); + } }, - buffer -> new AgeCelestialAnglePayload( - buffer.readResourceKey(Registries.DIMENSION), buffer.readFloat()) + buffer -> { + ResourceKey dimension = buffer.readResourceKey(Registries.DIMENSION); + float angle = buffer.readFloat(); + AgeSkyProfile.Style style = buffer.readEnum(AgeSkyProfile.Style.class); + int color = buffer.readInt(); + int count = buffer.readVarInt(); + if (count < 0 || count > 20) throw new IllegalArgumentException("Invalid celestial body count"); + java.util.List bodies = new java.util.ArrayList<>(count); + for (int i = 0; i < count; i++) bodies.add(new AgeSkyProfile.Body( + buffer.readEnum(AgeSkyProfile.Kind.class), buffer.readInt(), + buffer.readFloat(), buffer.readFloat(), buffer.readDouble(), buffer.readFloat(), + buffer.readBoolean())); + return new AgeCelestialAnglePayload(dimension, angle, + new AgeSkyProfile(style, color, bodies)); + } ); @Override diff --git a/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java b/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java index 41c253a..e9fc91c 100644 --- a/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java +++ b/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java @@ -61,7 +61,7 @@ public final class ModNetworking { } public static void register(RegisterPayloadHandlersEvent event) { - PayloadRegistrar registrar = event.registrar("17"); + PayloadRegistrar registrar = event.registrar("18"); registrar.playToServer(RequestDescriptiveBookScreenPayload.TYPE, RequestDescriptiveBookScreenPayload.STREAM_CODEC, ModNetworking::requestDescriptiveBookScreen); registrar.playToClient(OpenDescriptiveBookScreenPayload.TYPE, OpenDescriptiveBookScreenPayload.STREAM_CODEC, @@ -81,6 +81,8 @@ public final class ModNetworking { ClientLinkPreviewHandler::handle); registrar.playToClient(AgeCelestialAnglePayload.TYPE, AgeCelestialAnglePayload.STREAM_CODEC, ClientAgeCelestialAngles::handle); + registrar.playToServer(RequestAgeSkyPayload.TYPE, RequestAgeSkyPayload.STREAM_CODEC, + ModNetworking::requestAgeSky); registrar.playToClient(SereneAgeWhitelistPayload.TYPE, SereneAgeWhitelistPayload.STREAM_CODEC, ClientSereneAgeWhitelist::handle); registrar.playToClient(WritingDeskCatalogPayload.TYPE, WritingDeskCatalogPayload.STREAM_CODEC, @@ -93,6 +95,16 @@ public final class ModNetworking { ModNetworking::setLinkModifierSeed); } + private static void requestAgeSky(RequestAgeSkyPayload payload, IPayloadContext context) { + if (!(context.player() instanceof ServerPlayer player)) return; + // Portal renderers may request another Age. Only return registered Age data, + // never an arbitrary client-supplied dimension or generated terrain. + AgeRegistrySavedData.get(player.serverLevel()).definitions().stream() + .filter(age -> AgeDimensionService.dimensionKey(age).equals(payload.dimension())) + .findFirst() + .ifPresent(age -> AgeCelestialAngleSync.send(player, age)); + } + private static void setBookBinderTitle(SetBookBinderTitlePayload payload, IPayloadContext context) { if (context.player() instanceof ServerPlayer player && player.containerMenu instanceof BookBinderMenu menu @@ -348,8 +360,7 @@ public final class ModNetworking { /** Sky effects, not dimension IDs, decide the backdrop for custom Ages. */ private static int previewSkyColor(ServerLevel level, BlockPos origin) { - return AgeEnvironmentCompiler.previewSkyColor(level.dimensionType().effectsLocation(), - level.getBiome(origin).value().getSkyColor()); + return AgeEnvironmentCompiler.previewSkyColor(level, origin); } private static InteractionHand hand(boolean offhand) { diff --git a/src/main/java/net/dnorberg/rivencraft/network/RequestAgeSkyPayload.java b/src/main/java/net/dnorberg/rivencraft/network/RequestAgeSkyPayload.java new file mode 100644 index 0000000..10fb391 --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/network/RequestAgeSkyPayload.java @@ -0,0 +1,24 @@ +package net.dnorberg.rivencraft.network; + +import net.dnorberg.rivencraft.Rivencraft; +import net.minecraft.core.registries.Registries; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; + +/** Requests the saved sky profile when a renderer reaches an Age before dimension-change sync. */ +public record RequestAgeSkyPayload(ResourceKey dimension) implements CustomPacketPayload { + public static final Type TYPE = new Type<>( + ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "request_age_sky")); + public static final StreamCodec STREAM_CODEC = StreamCodec.of( + (buffer, payload) -> buffer.writeResourceKey(payload.dimension()), + buffer -> new RequestAgeSkyPayload(buffer.readResourceKey(Registries.DIMENSION))); + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/writing/SymbolSemantics.java b/src/main/java/net/dnorberg/rivencraft/writing/SymbolSemantics.java index 9f93785..3e42e0f 100644 --- a/src/main/java/net/dnorberg/rivencraft/writing/SymbolSemantics.java +++ b/src/main/java/net/dnorberg/rivencraft/writing/SymbolSemantics.java @@ -77,12 +77,18 @@ 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"), + SKY("sky", Kind.CHOICE, "overworld", "nether", "end", "static"), + SKY_COLOR("sky_color", Kind.CHOICE, + "red", "orange", "yellow", "green", "blue", "purple", "white", "black"), WEATHER("weather", Kind.CHOICE, "normal", "fast", "slow", "off", "always", "cloudy", "rain", "snow", "storm"), SEASON("season", Kind.CHOICE, "no_seasons", "seasons", "eternal_winter", "eternal_summer", "eternal_fall", "eternal_spring"), - CELESTIAL("celestial", Kind.CHOICE, "normal_time"), + CELESTIAL("celestial", Kind.CHOICE, "normal_time", "sun", "moon"), + CELESTIAL_COLOR("celestial_color", Kind.CHOICE, + "red", "orange", "yellow", "green", "blue", "purple", "white", "black"), + DIRECTION_MODIFIER("direction_modifier", Kind.CHOICE, "clockwise", "counterclockwise"), + SIZE_MODIFIER("size_modifier", Kind.NUMBER), PERIOD_MODIFIER("period_modifier", Kind.NUMBER), ANGLE_MODIFIER("angle_modifier", Kind.NUMBER), PHASE_MODIFIER("phase_modifier", Kind.NUMBER), @@ -109,6 +115,7 @@ public record SymbolSemantics(Role role, Value value) { if (!(value instanceof NumberValue number)) return false; return switch (this) { case PERIOD_MODIFIER -> number.amount() >= 0.0 && number.amount() <= 2.0; + case SIZE_MODIFIER -> number.amount() >= 0.25 && number.amount() <= 3.0; case ANGLE_MODIFIER, PHASE_MODIFIER -> number.amount() >= 0.0 && number.amount() < 360.0; default -> false; }; @@ -119,6 +126,7 @@ public record SymbolSemantics(Role role, Value value) { case RESOURCE -> "a canonical namespaced ID"; case CHOICE -> "one of " + choices.stream().sorted().toList(); case NUMBER -> this == PERIOD_MODIFIER ? "a finite number in [0, 2]" + : this == SIZE_MODIFIER ? "a finite number in [0.25, 3]" : "a finite number in [0, 360) degrees"; }; } diff --git a/src/main/java/net/dnorberg/rivencraft/writing/WritingGrammar.java b/src/main/java/net/dnorberg/rivencraft/writing/WritingGrammar.java index 74b7a38..267b990 100644 --- a/src/main/java/net/dnorberg/rivencraft/writing/WritingGrammar.java +++ b/src/main/java/net/dnorberg/rivencraft/writing/WritingGrammar.java @@ -96,7 +96,7 @@ public final class WritingGrammar { } pendingBlock = null; } - if (role == Role.ANGLE_MODIFIER || role == Role.WEATHER + if (role == Role.WEATHER && parsed.semantics().value() instanceof ChoiceValue choice && UNSUPPORTED_WEATHER.contains(choice.id())) { diagnostics.add(new Diagnostic(Code.UNSUPPORTED_ENVIRONMENT, index, @@ -142,7 +142,10 @@ public final class WritingGrammar { } for (Role required : List.of(Role.TERRAIN, Role.BIOME_CONTROLLER, Role.WEATHER, Role.LIGHTING, Role.CELESTIAL)) { - if (!singular.containsKey(required)) diagnostics.add(new Diagnostic(Code.INCOMPLETE, -1, + boolean present = required == Role.CELESTIAL + ? symbols.stream().anyMatch(symbol -> symbol.semantics().role() == Role.CELESTIAL) + : singular.containsKey(required); + if (!present) diagnostics.add(new Diagnostic(Code.INCOMPLETE, -1, "The Age compiler will fill the missing " + required.id() + " branch from the Age seed")); } if (controller != null && controller.semantics().value() instanceof ChoiceValue choice) { @@ -160,13 +163,15 @@ public final class WritingGrammar { private static boolean singular(Role role) { return switch (role) { - case TERRAIN, BIOME_CONTROLLER, WEATHER, SEASON, LIGHTING, SKY, CELESTIAL -> true; + case TERRAIN, BIOME_CONTROLLER, WEATHER, SEASON, LIGHTING, SKY -> true; default -> false; }; } private static boolean modifier(Role role) { - return role == Role.PERIOD_MODIFIER || role == Role.ANGLE_MODIFIER || role == Role.PHASE_MODIFIER; + return role == Role.PERIOD_MODIFIER || role == Role.ANGLE_MODIFIER + || role == Role.PHASE_MODIFIER || role == Role.SIZE_MODIFIER + || role == Role.CELESTIAL_COLOR || role == Role.DIRECTION_MODIFIER; } private static int stage(Role role) { @@ -177,8 +182,9 @@ public final class WritingGrammar { case WEATHER -> 3; case SEASON -> 4; case LIGHTING -> 5; - case SKY -> 6; - case PERIOD_MODIFIER, ANGLE_MODIFIER, PHASE_MODIFIER -> 7; + case SKY, SKY_COLOR -> 6; + case PERIOD_MODIFIER, ANGLE_MODIFIER, PHASE_MODIFIER, SIZE_MODIFIER, + CELESTIAL_COLOR, DIRECTION_MODIFIER -> 7; case CELESTIAL -> 8; case BLOCK, FEATURE, ENVIRONMENT, CORRUPTED -> 9; }; diff --git a/src/main/resources/assets/rivencraft/lang/en_us.json b/src/main/resources/assets/rivencraft/lang/en_us.json index a16aa72..c854865 100644 --- a/src/main/resources/assets/rivencraft/lang/en_us.json +++ b/src/main/resources/assets/rivencraft/lang/en_us.json @@ -223,6 +223,24 @@ "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.sky.static": "Static Sky", + "symbol.rivencraft.sky.static.description": "Fill the sky with shifting television static.", + "symbol.rivencraft.sky_color.red": "Red Sky Color", + "symbol.rivencraft.sky_color.red.description": "Tint the sky and linked portal red.", + "symbol.rivencraft.sky_color.orange": "Orange Sky Color", + "symbol.rivencraft.sky_color.orange.description": "Tint the sky and linked portal orange.", + "symbol.rivencraft.sky_color.yellow": "Yellow Sky Color", + "symbol.rivencraft.sky_color.yellow.description": "Tint the sky and linked portal yellow.", + "symbol.rivencraft.sky_color.green": "Green Sky Color", + "symbol.rivencraft.sky_color.green.description": "Tint the sky and linked portal green.", + "symbol.rivencraft.sky_color.blue": "Blue Sky Color", + "symbol.rivencraft.sky_color.blue.description": "Tint the sky and linked portal blue.", + "symbol.rivencraft.sky_color.purple": "Purple Sky Color", + "symbol.rivencraft.sky_color.purple.description": "Tint the sky and linked portal purple.", + "symbol.rivencraft.sky_color.white": "White Sky Color", + "symbol.rivencraft.sky_color.white.description": "Tint the sky and linked portal white.", + "symbol.rivencraft.sky_color.black": "Black Sky Color", + "symbol.rivencraft.sky_color.black.description": "Tint the sky and linked portal near-black.", "symbol.rivencraft.weather.normal": "Normal Weather", "symbol.rivencraft.weather.normal.description": "Use the normal weather cycle.", "symbol.rivencraft.weather.fast": "Fast Weather", @@ -249,6 +267,44 @@ "symbol.rivencraft.season.eternal_spring.description": "Hold this Age in midspring.", "symbol.rivencraft.celestial.normal_time": "Normal Time", "symbol.rivencraft.celestial.normal_time.description": "Use a normal-length celestial cycle.", + "symbol.rivencraft.celestial.sun": "Sun", + "symbol.rivencraft.celestial.sun.description": "Add one independently moving sun to the sky.", + "symbol.rivencraft.celestial.moon": "Moon", + "symbol.rivencraft.celestial.moon.description": "Add one independently moving moon to the sky.", + "symbol.rivencraft.celestial_color.red": "Red Celestial", + "symbol.rivencraft.celestial_color.red.description": "Color the next sun or moon red.", + "symbol.rivencraft.celestial_color.orange": "Orange Celestial", + "symbol.rivencraft.celestial_color.orange.description": "Color the next sun or moon orange.", + "symbol.rivencraft.celestial_color.yellow": "Yellow Celestial", + "symbol.rivencraft.celestial_color.yellow.description": "Color the next sun or moon yellow.", + "symbol.rivencraft.celestial_color.green": "Green Celestial", + "symbol.rivencraft.celestial_color.green.description": "Color the next sun or moon green.", + "symbol.rivencraft.celestial_color.blue": "Blue Celestial", + "symbol.rivencraft.celestial_color.blue.description": "Color the next sun or moon blue.", + "symbol.rivencraft.celestial_color.purple": "Purple Celestial", + "symbol.rivencraft.celestial_color.purple.description": "Color the next sun or moon purple.", + "symbol.rivencraft.celestial_color.white": "White Celestial", + "symbol.rivencraft.celestial_color.white.description": "Color the next sun or moon white.", + "symbol.rivencraft.celestial_color.black": "Black Celestial", + "symbol.rivencraft.celestial_color.black.description": "Blend black into the next sun or moon's color.", + "symbol.rivencraft.size.small": "Small Celestial", + "symbol.rivencraft.size.small.description": "Halve the apparent size of the next sun or moon.", + "symbol.rivencraft.size.normal": "Normal Celestial Size", + "symbol.rivencraft.size.normal.description": "Set the next sun or moon to normal apparent size.", + "symbol.rivencraft.size.large": "Large Celestial", + "symbol.rivencraft.size.large.description": "Double the apparent size of the next sun or moon.", + "symbol.rivencraft.angle.north": "North Celestial Angle", + "symbol.rivencraft.angle.north.description": "Orient the next sun or moon toward the north.", + "symbol.rivencraft.angle.east": "East Celestial Angle", + "symbol.rivencraft.angle.east.description": "Orient the next sun or moon toward the east.", + "symbol.rivencraft.angle.south": "South Celestial Angle", + "symbol.rivencraft.angle.south.description": "Orient the next sun or moon toward the south.", + "symbol.rivencraft.angle.west": "West Celestial Angle", + "symbol.rivencraft.angle.west.description": "Orient the next sun or moon toward the west.", + "symbol.rivencraft.direction.clockwise": "Clockwise Motion", + "symbol.rivencraft.direction.clockwise.description": "Move the next sun or moon clockwise in its orbital plane.", + "symbol.rivencraft.direction.counterclockwise": "Counterclockwise Motion", + "symbol.rivencraft.direction.counterclockwise.description": "Move the next sun or moon counterclockwise in its orbital plane.", "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", diff --git a/src/main/resources/assets/rivencraft/models/block/crystal_portal.json b/src/main/resources/assets/rivencraft/models/block/crystal_portal.json index 1cb084c..3e7eadc 100644 --- a/src/main/resources/assets/rivencraft/models/block/crystal_portal.json +++ b/src/main/resources/assets/rivencraft/models/block/crystal_portal.json @@ -1 +1 @@ -{"render_type":"minecraft:translucent","ambientocclusion":false,"textures":{"particle":"rivencraft:block/crystal_portal","portal":"rivencraft:block/crystal_portal"},"elements":[{"from":[0,0,6],"to":[16,16,10],"shade":false,"faces":{"north":{"texture":"#portal"},"south":{"texture":"#portal"}}}]} +{"render_type":"minecraft:translucent","ambientocclusion":false,"textures":{"particle":"rivencraft:block/crystal_portal","portal":"rivencraft:block/crystal_portal"},"elements":[{"from":[0,0,6],"to":[16,16,10],"shade":false,"faces":{"north":{"texture":"#portal","tintindex":0},"south":{"texture":"#portal","tintindex":0}}}]} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/angle/east.json b/src/main/resources/data/rivencraft/rivencraft/symbols/angle/east.json new file mode 100644 index 0000000..cd0bb2a --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/angle/east.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.angle.east","description":"symbol.rivencraft.angle.east.description","category":"rivencraft:celestial","semantics":{"role":"angle_modifier","value":90.0},"poem":"Transform East Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/angle/north.json b/src/main/resources/data/rivencraft/rivencraft/symbols/angle/north.json new file mode 100644 index 0000000..8730e05 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/angle/north.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.angle.north","description":"symbol.rivencraft.angle.north.description","category":"rivencraft:celestial","semantics":{"role":"angle_modifier","value":0.0},"poem":"Transform North Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/angle/south.json b/src/main/resources/data/rivencraft/rivencraft/symbols/angle/south.json new file mode 100644 index 0000000..729ef6f --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/angle/south.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.angle.south","description":"symbol.rivencraft.angle.south.description","category":"rivencraft:celestial","semantics":{"role":"angle_modifier","value":180.0},"poem":"Transform South Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/angle/west.json b/src/main/resources/data/rivencraft/rivencraft/symbols/angle/west.json new file mode 100644 index 0000000..1264790 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/angle/west.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.angle.west","description":"symbol.rivencraft.angle.west.description","category":"rivencraft:celestial","semantics":{"role":"angle_modifier","value":270.0},"poem":"Transform West Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/celestial/moon.json b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial/moon.json new file mode 100644 index 0000000..f63b4d4 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial/moon.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.celestial.moon","description":"symbol.rivencraft.celestial.moon.description","category":"rivencraft:celestial","semantics":{"role":"celestial","value":"moon"},"poem":"Transform Time Shadow Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/celestial/sun.json b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial/sun.json new file mode 100644 index 0000000..31f39df --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial/sun.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.celestial.sun","description":"symbol.rivencraft.celestial.sun.description","category":"rivencraft:celestial","semantics":{"role":"celestial","value":"sun"},"poem":"Transform Time Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/black.json b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/black.json new file mode 100644 index 0000000..d0b0d3c --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/black.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.celestial_color.black","description":"symbol.rivencraft.celestial_color.black.description","category":"rivencraft:celestial","semantics":{"role":"celestial_color","value":"black"},"poem":"Transform Black Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/blue.json b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/blue.json new file mode 100644 index 0000000..96b6dc9 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/blue.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.celestial_color.blue","description":"symbol.rivencraft.celestial_color.blue.description","category":"rivencraft:celestial","semantics":{"role":"celestial_color","value":"blue"},"poem":"Transform Blue Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/green.json b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/green.json new file mode 100644 index 0000000..446e47b --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/green.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.celestial_color.green","description":"symbol.rivencraft.celestial_color.green.description","category":"rivencraft:celestial","semantics":{"role":"celestial_color","value":"green"},"poem":"Transform Green Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/orange.json b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/orange.json new file mode 100644 index 0000000..8eacabb --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/orange.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.celestial_color.orange","description":"symbol.rivencraft.celestial_color.orange.description","category":"rivencraft:celestial","semantics":{"role":"celestial_color","value":"orange"},"poem":"Transform Orange Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/purple.json b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/purple.json new file mode 100644 index 0000000..fea732b --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/purple.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.celestial_color.purple","description":"symbol.rivencraft.celestial_color.purple.description","category":"rivencraft:celestial","semantics":{"role":"celestial_color","value":"purple"},"poem":"Transform Purple Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/red.json b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/red.json new file mode 100644 index 0000000..27e83cc --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/red.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.celestial_color.red","description":"symbol.rivencraft.celestial_color.red.description","category":"rivencraft:celestial","semantics":{"role":"celestial_color","value":"red"},"poem":"Transform Red Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/white.json b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/white.json new file mode 100644 index 0000000..e77160b --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/white.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.celestial_color.white","description":"symbol.rivencraft.celestial_color.white.description","category":"rivencraft:celestial","semantics":{"role":"celestial_color","value":"white"},"poem":"Transform White Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/yellow.json b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/yellow.json new file mode 100644 index 0000000..9195db9 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/celestial_color/yellow.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.celestial_color.yellow","description":"symbol.rivencraft.celestial_color.yellow.description","category":"rivencraft:celestial","semantics":{"role":"celestial_color","value":"yellow"},"poem":"Transform Yellow Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/direction/clockwise.json b/src/main/resources/data/rivencraft/rivencraft/symbols/direction/clockwise.json new file mode 100644 index 0000000..b99e016 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/direction/clockwise.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.direction.clockwise","description":"symbol.rivencraft.direction.clockwise.description","category":"rivencraft:celestial","semantics":{"role":"direction_modifier","value":"clockwise"},"poem":"Transform Time Clockwise Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/direction/counterclockwise.json b/src/main/resources/data/rivencraft/rivencraft/symbols/direction/counterclockwise.json new file mode 100644 index 0000000..8eeb6ea --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/direction/counterclockwise.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.direction.counterclockwise","description":"symbol.rivencraft.direction.counterclockwise.description","category":"rivencraft:celestial","semantics":{"role":"direction_modifier","value":"counterclockwise"},"poem":"Transform Time Reverse Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/size/large.json b/src/main/resources/data/rivencraft/rivencraft/symbols/size/large.json new file mode 100644 index 0000000..3def32b --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/size/large.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.size.large","description":"symbol.rivencraft.size.large.description","category":"rivencraft:celestial","semantics":{"role":"size_modifier","value":2.0},"poem":"Transform Large Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/size/normal.json b/src/main/resources/data/rivencraft/rivencraft/symbols/size/normal.json new file mode 100644 index 0000000..e4ee0c0 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/size/normal.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.size.normal","description":"symbol.rivencraft.size.normal.description","category":"rivencraft:celestial","semantics":{"role":"size_modifier","value":1.0},"poem":"Transform Balance Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/size/small.json b/src/main/resources/data/rivencraft/rivencraft/symbols/size/small.json new file mode 100644 index 0000000..e5d3852 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/size/small.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.size.small","description":"symbol.rivencraft.size.small.description","category":"rivencraft:celestial","semantics":{"role":"size_modifier","value":0.5},"poem":"Transform Small Illuminate Motion"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/sky/static.json b/src/main/resources/data/rivencraft/rivencraft/symbols/sky/static.json new file mode 100644 index 0000000..f25a31a --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/sky/static.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.sky.static","description":"symbol.rivencraft.sky.static.description","category":"rivencraft:sky","semantics":{"role":"sky","value":"static"},"poem":"Ethereal Void Sky Flux"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/black.json b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/black.json new file mode 100644 index 0000000..b0f51b2 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/black.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.sky_color.black","description":"symbol.rivencraft.sky_color.black.description","category":"rivencraft:sky","semantics":{"role":"sky_color","value":"black"},"poem":"Ethereal Black Sky Balance"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/blue.json b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/blue.json new file mode 100644 index 0000000..347377a --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/blue.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.sky_color.blue","description":"symbol.rivencraft.sky_color.blue.description","category":"rivencraft:sky","semantics":{"role":"sky_color","value":"blue"},"poem":"Ethereal Blue Sky Balance"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/green.json b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/green.json new file mode 100644 index 0000000..2619acc --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/green.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.sky_color.green","description":"symbol.rivencraft.sky_color.green.description","category":"rivencraft:sky","semantics":{"role":"sky_color","value":"green"},"poem":"Ethereal Green Sky Balance"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/orange.json b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/orange.json new file mode 100644 index 0000000..8f291d1 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/orange.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.sky_color.orange","description":"symbol.rivencraft.sky_color.orange.description","category":"rivencraft:sky","semantics":{"role":"sky_color","value":"orange"},"poem":"Ethereal Orange Sky Balance"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/purple.json b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/purple.json new file mode 100644 index 0000000..f6a4451 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/purple.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.sky_color.purple","description":"symbol.rivencraft.sky_color.purple.description","category":"rivencraft:sky","semantics":{"role":"sky_color","value":"purple"},"poem":"Ethereal Purple Sky Balance"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/red.json b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/red.json new file mode 100644 index 0000000..ef0b764 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/red.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.sky_color.red","description":"symbol.rivencraft.sky_color.red.description","category":"rivencraft:sky","semantics":{"role":"sky_color","value":"red"},"poem":"Ethereal Red Sky Balance"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/white.json b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/white.json new file mode 100644 index 0000000..257ff6d --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/white.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.sky_color.white","description":"symbol.rivencraft.sky_color.white.description","category":"rivencraft:sky","semantics":{"role":"sky_color","value":"white"},"poem":"Ethereal White Sky Balance"} diff --git a/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/yellow.json b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/yellow.json new file mode 100644 index 0000000..d34aab8 --- /dev/null +++ b/src/main/resources/data/rivencraft/rivencraft/symbols/sky_color/yellow.json @@ -0,0 +1 @@ +{"schema_version":2,"name":"symbol.rivencraft.sky_color.yellow","description":"symbol.rivencraft.sky_color.yellow.description","category":"rivencraft:sky","semantics":{"role":"sky_color","value":"yellow"},"poem":"Ethereal Yellow Sky Balance"} diff --git a/src/main/resources/rivencraft.mixins.json b/src/main/resources/rivencraft.mixins.json index f6c6860..761a490 100644 --- a/src/main/resources/rivencraft.mixins.json +++ b/src/main/resources/rivencraft.mixins.json @@ -5,12 +5,14 @@ "compatibilityLevel": "JAVA_21", "mixins": [ "AgeDimensionSeedMixin", + "LevelSkyBrightnessMixin", "AgeDungeonPlacementMixin", "AgeStructureBiomeMixin", "MobDropChanceAccessor", "StructurePlacementAccessor" ], "client": [ + "client.ClientLevelSkyColorMixin", "client.ClientLevelSoundMixin", "client.ImmersivePortalsGuiRenderingMixin", "client.LevelRendererMixin"