Public Access
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ac4c499ab |
No files matched your search
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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<ClientGamePacketListener> getUpdatePacket() {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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<NoiseGeneratorSettings> 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()
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -51,6 +51,10 @@ public record AgeInstability(int version, int score, List<Contribution> 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<Contribution> contributions = new ArrayList<>();
|
||||
if (plan == null) {
|
||||
@@ -82,6 +86,17 @@ public record AgeInstability(int version, int score, List<Contribution> 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<Contribution> 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(),
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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<Body> 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<Body> 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<Body> bodies = new ArrayList<>();
|
||||
Set<Integer> 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<Integer> 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<Integer> 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<Integer> 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) { }
|
||||
}
|
||||
@@ -52,9 +52,11 @@ public final class EnvironmentWritingGameTests {
|
||||
public static void environmentPagesEnterCreativeAndJeiFeed(GameTestHelper helper) {
|
||||
Set<String> 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(
|
||||
|
||||
@@ -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<ResourceKey<Level>, Float> ANGLES = new ConcurrentHashMap<>();
|
||||
private static final Map<ResourceKey<Level>, AgeSkyProfile> SKIES = new ConcurrentHashMap<>();
|
||||
private static final Map<ResourceKey<Level>, 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<Level> 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<Level> dimension) {
|
||||
return ANGLES.getOrDefault(dimension, 0.0F);
|
||||
}
|
||||
|
||||
public static int skyColor(ResourceKey<Level> dimension) {
|
||||
return sky(dimension).color();
|
||||
}
|
||||
|
||||
public static boolean staticSky(ResourceKey<Level> dimension) {
|
||||
return sky(dimension).style() == AgeSkyProfile.Style.STATIC;
|
||||
}
|
||||
|
||||
public static AgeSkyProfile sky(ResourceKey<Level> dimension) {
|
||||
return SKIES.getOrDefault(dimension,
|
||||
new AgeSkyProfile(AgeSkyProfile.Style.INHERITED, -1, java.util.List.of()));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<Vec3> 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<Float> 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);
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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<Level> dimension, float angle)
|
||||
/** Synchronizes an Age's saved celestial-plane rotation and authored sky to clients. */
|
||||
public record AgeCelestialAnglePayload(ResourceKey<Level> dimension, float angle, AgeSkyProfile sky)
|
||||
implements CustomPacketPayload {
|
||||
public static final Type<AgeCelestialAnglePayload> TYPE = new Type<>(
|
||||
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "age_celestial_angle")
|
||||
@@ -20,9 +21,34 @@ public record AgeCelestialAnglePayload(ResourceKey<Level> 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<Level> 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<AgeSkyProfile.Body> 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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<Level> dimension) implements CustomPacketPayload {
|
||||
public static final Type<RequestAgeSkyPayload> TYPE = new Type<>(
|
||||
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "request_age_sky"));
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, RequestAgeSkyPayload> STREAM_CODEC = StreamCodec.of(
|
||||
(buffer, payload) -> buffer.writeResourceKey(payload.dimension()),
|
||||
buffer -> new RequestAgeSkyPayload(buffer.readResourceKey(Registries.DIMENSION)));
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}}}]}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -5,12 +5,14 @@
|
||||
"compatibilityLevel": "JAVA_21",
|
||||
"mixins": [
|
||||
"AgeDimensionSeedMixin",
|
||||
"LevelSkyBrightnessMixin",
|
||||
"AgeDungeonPlacementMixin",
|
||||
"AgeStructureBiomeMixin",
|
||||
"MobDropChanceAccessor",
|
||||
"StructurePlacementAccessor"
|
||||
],
|
||||
"client": [
|
||||
"client.ClientLevelSkyColorMixin",
|
||||
"client.ClientLevelSoundMixin",
|
||||
"client.ImmersivePortalsGuiRenderingMixin",
|
||||
"client.LevelRendererMixin"
|
||||
|
||||
Reference in new issue
Block a user