Apply Age celestial angles
Build / build (push) Canceled after 0s

This commit is contained in:
dnorberg committed 2026-09-22 22:33:11 -04:00
1 parent 88af167f62
commit 08caa98c0b
10 files changed
+163 -4

No files matched your search

+5
View File
@@ -355,6 +355,11 @@ Age's selected terrain profile. End biome columns generate from End stone;
Nether biome columns use Minecraft's Nether surface rules for netherrack,
soul sand, basalt, and nylium instead of inheriting Overworld blocks.
Version 0.2.26.0 applies each Age's persisted celestial angle to the client
sky. The sun, moon, and stars follow a plane rotated by the saved Legacy-style
angle in normal gameplay and Immersive Portals previews, while vanilla
dimensions retain their standard celestial path.
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
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.
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.2.25.1
mod_version=0.2.26.0
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.
# This should match the base package used for the mod sources.
@@ -9,6 +9,7 @@ import net.dnorberg.rivencraft.registry.ModBlocks;
import net.dnorberg.rivencraft.registry.ModBlockEntities;
import net.dnorberg.rivencraft.registry.ModBiomeSources;
import net.dnorberg.rivencraft.event.BookStandConversionHandler;
import net.dnorberg.rivencraft.event.AgeCelestialAngleSync;
import net.dnorberg.rivencraft.network.ModNetworking;
import net.dnorberg.rivencraft.config.RivencraftConfig;
import net.dnorberg.rivencraft.config.RivencraftClientConfig;
@@ -40,6 +41,8 @@ public final class Rivencraft {
ModCreativeTabs.register(modEventBus);
NeoForge.EVENT_BUS.addListener(BookStandConversionHandler::onRightClickBlock);
NeoForge.EVENT_BUS.addListener(AgeWeatherService::onLevelTickPost);
NeoForge.EVENT_BUS.addListener(AgeCelestialAngleSync::onPlayerLoggedIn);
NeoForge.EVENT_BUS.addListener(AgeCelestialAngleSync::onPlayerChangedDimension);
LOGGER.info("Initializing Rivencraft");
}
}
@@ -0,0 +1,24 @@
package net.dnorberg.rivencraft.client;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.dnorberg.rivencraft.network.AgeCelestialAnglePayload;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import net.neoforged.neoforge.network.handling.IPayloadContext;
/** 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 ClientAgeCelestialAngles() {
}
public static void handle(AgeCelestialAnglePayload payload, IPayloadContext context) {
ANGLES.put(payload.dimension(), payload.angle());
}
public static float get(ResourceKey<Level> dimension) {
return ANGLES.getOrDefault(dimension, 0.0F);
}
}
@@ -0,0 +1,43 @@
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.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 {
private AgeCelestialAngleSync() {
}
public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
if (!(event.getEntity() instanceof ServerPlayer player)) {
return;
}
for (AgeDefinition age : AgeRegistrySavedData.get(player.serverLevel()).definitions()) {
send(player, age);
}
}
public static void onPlayerChangedDimension(PlayerEvent.PlayerChangedDimensionEvent event) {
if (!(event.getEntity() instanceof ServerPlayer player)) {
return;
}
AgeRegistrySavedData.get(player.serverLevel()).definitions().stream()
.filter(age -> AgeDimensionService.dimensionKey(age).equals(event.getTo()))
.findFirst()
.ifPresent(age -> send(player, age));
}
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()));
}
}
@@ -0,0 +1,31 @@
package net.dnorberg.rivencraft.mixin.client;
import com.mojang.math.Axis;
import net.dnorberg.rivencraft.client.ClientAgeCelestialAngles;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.renderer.LevelRenderer;
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.Redirect;
/** Tilts the vanilla sun, moon, and star plane by each Age's saved angle. */
@Mixin(LevelRenderer.class)
public abstract class LevelRendererMixin {
@Shadow
private ClientLevel level;
@Redirect(
method = "renderSky",
at = @At(
value = "INVOKE",
target = "Lcom/mojang/math/Axis;rotationDegrees(F)Lorg/joml/Quaternionf;",
ordinal = 3
)
)
private Quaternionf rivencraft$rotateCelestialPlane(Axis axis, float degrees) {
float angle = ClientAgeCelestialAngles.get(level.dimension());
return new Quaternionf(Axis.ZP.rotationDegrees(angle)).mul(axis.rotationDegrees(degrees));
}
}
@@ -0,0 +1,32 @@
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;
/** Synchronizes an Age's saved celestial-plane rotation to clients. */
public record AgeCelestialAnglePayload(ResourceKey<Level> dimension, float angle)
implements CustomPacketPayload {
public static final Type<AgeCelestialAnglePayload> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "age_celestial_angle")
);
public static final StreamCodec<RegistryFriendlyByteBuf, AgeCelestialAnglePayload> STREAM_CODEC =
StreamCodec.of(
(buffer, payload) -> {
buffer.writeResourceKey(payload.dimension());
buffer.writeFloat(payload.angle());
},
buffer -> new AgeCelestialAnglePayload(
buffer.readResourceKey(Registries.DIMENSION), buffer.readFloat())
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}
@@ -8,6 +8,8 @@ import net.dnorberg.rivencraft.book.AgeDimensionService;
import net.dnorberg.rivencraft.block.entity.BookStandBlockEntity;
import net.dnorberg.rivencraft.client.ClientLinkPreviewHandler;
import net.dnorberg.rivencraft.client.ClientBookInteractionHandler;
import net.dnorberg.rivencraft.client.ClientAgeCelestialAngles;
import net.dnorberg.rivencraft.event.AgeCelestialAngleSync;
import net.dnorberg.rivencraft.item.DescriptiveBookItem;
import net.dnorberg.rivencraft.item.LinkingBookItem;
import net.dnorberg.rivencraft.registry.ModDataComponents;
@@ -46,7 +48,7 @@ public final class ModNetworking {
}
public static void register(RegisterPayloadHandlersEvent event) {
PayloadRegistrar registrar = event.registrar("8");
PayloadRegistrar registrar = event.registrar("9");
registrar.playToServer(RequestDescriptiveBookScreenPayload.TYPE, RequestDescriptiveBookScreenPayload.STREAM_CODEC,
ModNetworking::requestDescriptiveBookScreen);
registrar.playToClient(OpenDescriptiveBookScreenPayload.TYPE, OpenDescriptiveBookScreenPayload.STREAM_CODEC,
@@ -64,6 +66,8 @@ public final class ModNetworking {
(payload, context) -> releaseLinkPreview(context));
registrar.playToClient(LinkPreviewPayload.TYPE, LinkPreviewPayload.STREAM_CODEC,
ClientLinkPreviewHandler::handle);
registrar.playToClient(AgeCelestialAnglePayload.TYPE, AgeCelestialAnglePayload.STREAM_CODEC,
ClientAgeCelestialAngles::handle);
}
private static void requestDescriptiveBookScreen(RequestDescriptiveBookScreenPayload payload, IPayloadContext context) {
@@ -222,6 +226,11 @@ public final class ModNetworking {
return;
}
AgeRegistrySavedData.get(player.serverLevel()).definitions().stream()
.filter(age -> AgeDimensionService.dimensionKey(age).equals(target.location().dimension()))
.findFirst()
.ifPresent(age -> AgeCelestialAngleSync.send(player, age));
ServerLevel level = player.getServer().getLevel(target.location().dimension());
if (level == null) {
return;
+12
View File
@@ -0,0 +1,12 @@
{
"required": true,
"minVersion": "0.8",
"package": "net.dnorberg.rivencraft.mixin",
"compatibilityLevel": "JAVA_21",
"client": [
"client.LevelRendererMixin"
],
"injectors": {
"defaultRequire": 1
}
}
@@ -50,8 +50,8 @@ A spiritual successor and modern port of Mystcraft for Minecraft 1.21.1 on NeoFo
'''
# The [[mixins]] block allows you to declare your mixin config to FML so that it gets loaded.
#[[mixins]]
#config="${mod_id}.mixins.json"
[[mixins]]
config="${mod_id}.mixins.json"
# The [[accessTransformers]] block allows you to declare where your AT file is.
# If this block is omitted, a fallback attempt will be made to load an AT from META-INF/accesstransformer.cfg