Restore Zero Length frozen Age time
Build / build (push) Canceled after 0s

This commit is contained in:
dnorberg committed 2026-09-22 16:42:31 -04:00
1 parent 8bb5e16720
commit 2b18f776f2
8 files changed
+77 -12

No files matched your search

+5
View File
@@ -299,6 +299,11 @@ later loads continue from saved world time. Celestial angle remains persisted
for a future custom sky renderer because vanilla dimension data cannot rotate for a future custom sky renderer because vanilla dimension data cannot rotate
the sun's render plane. the sun's render plane.
Version 0.2.20.1 restores Legacy's Zero Length celestial modifier as a rarer
random outcome using its effective one-in-seven grammar weight. Zero Length
freezes an Age at its generated phase without changing the global daylight
cycle, while nonzero Ages retain their independently scaled clocks.
Version 0.2.14.0 restores the legacy Linking and Descriptive Book artwork for Version 0.2.14.0 restores the legacy Linking and Descriptive Book artwork for
inventory items and gives each book its matching open-book texture when placed inventory items and gives each book its matching open-book texture when placed
on a Book Stand. Return Linking Books created inside another Age also retain and on a Book Stand. Return Linking Books created inside another Age also retain and
+1 -1
View File
@@ -35,7 +35,7 @@ mod_name=Rivencraft
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. # 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 mod_license=LGPL-3.0-or-later
# The mod version. Rivencraft uses the x.y.z.b policy documented in README.md. # The mod version. Rivencraft uses the x.y.z.b policy documented in README.md.
mod_version=0.2.20.0 mod_version=0.2.20.1
immersive_portals_version=6.0.7 immersive_portals_version=6.0.7
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
# This should match the base package used for the mod sources. # This should match the base package used for the mod sources.
@@ -13,6 +13,7 @@ import net.dnorberg.rivencraft.config.RivencraftConfig;
import net.dnorberg.rivencraft.config.RivencraftClientConfig; import net.dnorberg.rivencraft.config.RivencraftClientConfig;
import net.dnorberg.rivencraft.book.AgeDimensionService; import net.dnorberg.rivencraft.book.AgeDimensionService;
import net.dnorberg.rivencraft.book.AgeWeatherService; import net.dnorberg.rivencraft.book.AgeWeatherService;
import net.dnorberg.rivencraft.book.AgeCelestialService;
import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModContainer; import net.neoforged.fml.ModContainer;
import net.neoforged.fml.common.Mod; import net.neoforged.fml.common.Mod;
@@ -38,6 +39,7 @@ public final class Rivencraft {
ModCreativeTabs.register(modEventBus); ModCreativeTabs.register(modEventBus);
NeoForge.EVENT_BUS.addListener(BookStandConversionHandler::onRightClickBlock); NeoForge.EVENT_BUS.addListener(BookStandConversionHandler::onRightClickBlock);
NeoForge.EVENT_BUS.addListener(AgeWeatherService::onLevelTickPost); NeoForge.EVENT_BUS.addListener(AgeWeatherService::onLevelTickPost);
NeoForge.EVENT_BUS.addListener(AgeCelestialService::onLevelTickPost);
LOGGER.info("Initializing Rivencraft"); LOGGER.info("Initializing Rivencraft");
} }
} }
@@ -0,0 +1,31 @@
package net.dnorberg.rivencraft.book;
import java.util.Map;
import java.util.WeakHashMap;
import net.minecraft.server.level.ServerLevel;
import net.neoforged.neoforge.event.tick.LevelTickEvent;
/** Holds Zero Length Age clocks without changing the server-wide daylight gamerule. */
public final class AgeCelestialService {
private static final Map<ServerLevel, Long> FROZEN_TIMES = new WeakHashMap<>();
private AgeCelestialService() {
}
public static void freezeAt(ServerLevel level, long dayTime) {
FROZEN_TIMES.put(level, dayTime);
}
public static void unfreeze(ServerLevel level) {
FROZEN_TIMES.remove(level);
}
public static void onLevelTickPost(LevelTickEvent.Post event) {
if (event.getLevel() instanceof ServerLevel level) {
Long frozenTime = FROZEN_TIMES.get(level);
if (frozenTime != null && level.getDayTime() != frozenTime) {
level.setDayTime(frozenTime);
}
}
}
}
@@ -21,7 +21,7 @@ public record AgeDefinition(
public static final ResourceLocation RANDOM_VANILLA_PROFILE = ResourceLocation.fromNamespaceAndPath( public static final ResourceLocation RANDOM_VANILLA_PROFILE = ResourceLocation.fromNamespaceAndPath(
Rivencraft.MOD_ID, "random_vanilla" Rivencraft.MOD_ID, "random_vanilla"
); );
public static final int CURRENT_GENERATION_VERSION = 6; public static final int CURRENT_GENERATION_VERSION = 7;
public AgeDefinition { public AgeDefinition {
Objects.requireNonNull(id, "id"); Objects.requireNonNull(id, "id");
@@ -103,12 +103,21 @@ public final class AgeDimensionService {
} }
private static void applyCelestialClock(ServerLevel level, AgeDefinition age) { private static void applyCelestialClock(ServerLevel level, AgeDefinition age) {
level.setDayTimePerTick((float)(1.0D / age.traits().celestialPeriod())); if (age.traits().hasStoppedCelestialCycle()) {
level.setDayTimePerTick(-1.0F);
AgeCelestialService.freezeAt(level, level.getDayTime());
} else {
AgeCelestialService.unfreeze(level);
level.setDayTimePerTick((float)(1.0D / age.traits().celestialPeriod()));
}
} }
private static void applyInitialCelestialPhase(ServerLevel level, AgeDefinition age) { private static void applyInitialCelestialPhase(ServerLevel level, AgeDefinition age) {
long phaseTime = (long)(age.traits().celestialPhase() * Level.TICKS_PER_DAY); long phaseTime = (long)(age.traits().celestialPhase() * Level.TICKS_PER_DAY);
level.setDayTime(phaseTime); level.setDayTime(phaseTime);
if (age.traits().hasStoppedCelestialCycle()) {
AgeCelestialService.freezeAt(level, phaseTime);
}
} }
private static void synchronizeImmersivePortals(MinecraftServer server) { private static void synchronizeImmersivePortals(MinecraftServer server) {
@@ -13,6 +13,7 @@ public record AgeTraits(
) { ) {
public static final double MIN_CELESTIAL_PERIOD = 0.2D; public static final double MIN_CELESTIAL_PERIOD = 0.2D;
public static final double MAX_CELESTIAL_PERIOD = 2.0D; public static final double MAX_CELESTIAL_PERIOD = 2.0D;
public static final double STOPPED_CELESTIAL_PERIOD = 0.0D;
public static final float MIN_CELESTIAL_ANGLE = 0.0F; public static final float MIN_CELESTIAL_ANGLE = 0.0F;
public static final float MAX_CELESTIAL_ANGLE = 360.0F; public static final float MAX_CELESTIAL_ANGLE = 360.0F;
public static final float MIN_CELESTIAL_PHASE = 0.0F; public static final float MIN_CELESTIAL_PHASE = 0.0F;
@@ -27,7 +28,8 @@ public record AgeTraits(
if (biomeDistribution == null || lighting == null || weather == null) { if (biomeDistribution == null || lighting == null || weather == null) {
throw new IllegalArgumentException("Age trait choices cannot be null"); throw new IllegalArgumentException("Age trait choices cannot be null");
} }
if (celestialPeriod < MIN_CELESTIAL_PERIOD || celestialPeriod > MAX_CELESTIAL_PERIOD) { if (celestialPeriod != STOPPED_CELESTIAL_PERIOD
&& (celestialPeriod < MIN_CELESTIAL_PERIOD || celestialPeriod > MAX_CELESTIAL_PERIOD)) {
throw new IllegalArgumentException("Celestial period is outside the legacy range"); throw new IllegalArgumentException("Celestial period is outside the legacy range");
} }
if (celestialAngle < MIN_CELESTIAL_ANGLE || celestialAngle >= MAX_CELESTIAL_ANGLE) { if (celestialAngle < MIN_CELESTIAL_ANGLE || celestialAngle >= MAX_CELESTIAL_ANGLE) {
@@ -40,17 +42,27 @@ public record AgeTraits(
public static AgeTraits select(long seed) { public static AgeTraits select(long seed) {
Random random = new Random(seed ^ 0x524956454E435241L); Random random = new Random(seed ^ 0x524956454E435241L);
BiomeDistribution biomeDistribution = randomChoice(random, BiomeDistribution.values());
Lighting lighting = randomChoice(random, Lighting.values());
Weather weather = randomChoice(random, Weather.values());
// Legacy's PERIOD_BASIC grammar gives ModZero rank 2 and its other
// three length modifiers rank 1, producing an effective 1-in-7 weight.
boolean stopped = random.nextInt(7) == 0;
return new AgeTraits( return new AgeTraits(
randomChoice(random, BiomeDistribution.values()), biomeDistribution,
randomChoice(random, Lighting.values()), lighting,
randomChoice(random, Weather.values()), weather,
MIN_CELESTIAL_PERIOD stopped ? STOPPED_CELESTIAL_PERIOD : MIN_CELESTIAL_PERIOD
+ random.nextDouble() * (MAX_CELESTIAL_PERIOD - MIN_CELESTIAL_PERIOD), + random.nextDouble() * (MAX_CELESTIAL_PERIOD - MIN_CELESTIAL_PERIOD),
random.nextFloat() * MAX_CELESTIAL_ANGLE, random.nextFloat() * MAX_CELESTIAL_ANGLE,
random.nextFloat() random.nextFloat()
); );
} }
public boolean hasStoppedCelestialCycle() {
return celestialPeriod == STOPPED_CELESTIAL_PERIOD;
}
public static <E extends Enum<E>> E parseOrDefault(String value, E fallback) { public static <E extends Enum<E>> E parseOrDefault(String value, E fallback) {
try { try {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -121,7 +121,8 @@ public final class LinkingGameTests {
"New Ages must record the current generation algorithm version"); "New Ages must record the current generation algorithm version");
helper.assertValueEqual(first.traits(), AgeTraits.select(first.seed()), helper.assertValueEqual(first.traits(), AgeTraits.select(first.seed()),
"A page-free book must persist the deterministic traits selected from its seed"); "A page-free book must persist the deterministic traits selected from its seed");
helper.assertTrue(first.traits().celestialPeriod() >= AgeTraits.MIN_CELESTIAL_PERIOD helper.assertTrue(first.traits().hasStoppedCelestialCycle()
|| first.traits().celestialPeriod() >= AgeTraits.MIN_CELESTIAL_PERIOD
&& first.traits().celestialPeriod() <= AgeTraits.MAX_CELESTIAL_PERIOD, && first.traits().celestialPeriod() <= AgeTraits.MAX_CELESTIAL_PERIOD,
"The celestial period must remain inside Mystcraft Legacy's random range"); "The celestial period must remain inside Mystcraft Legacy's random range");
helper.assertTrue(first.seed() != another.seed(), "Distinct Age UUIDs must receive distinct random seeds"); helper.assertTrue(first.seed() != another.seed(), "Distinct Age UUIDs must receive distinct random seeds");
@@ -186,9 +187,14 @@ public final class LinkingGameTests {
? 0.25F : 0.0F; ? 0.25F : 0.0F;
helper.assertTrue(Math.abs(level.dimensionType().ambientLight() - expectedAmbientLight) < 0.0001F, helper.assertTrue(Math.abs(level.dimensionType().ambientLight() - expectedAmbientLight) < 0.0001F,
"The runtime dimension must apply the persisted lighting trait"); "The runtime dimension must apply the persisted lighting trait");
float expectedDaySpeed = (float)(1.0D / definition.traits().celestialPeriod()); if (definition.traits().hasStoppedCelestialCycle()) {
helper.assertTrue(Math.abs(level.getDayTimePerTick() - expectedDaySpeed) < 0.0001F, helper.assertTrue(level.getDayTimePerTick() < 0.0F,
"The runtime dimension must apply the persisted celestial period"); "A Zero Length Age must leave vanilla clock advancement under the freeze controller");
} else {
float expectedDaySpeed = (float)(1.0D / definition.traits().celestialPeriod());
helper.assertTrue(Math.abs(level.getDayTimePerTick() - expectedDaySpeed) < 0.0001F,
"The runtime dimension must apply the persisted celestial period");
}
} }
helper.succeed(); helper.succeed();
} }