diff --git a/docs/PORTING.md b/docs/PORTING.md index f7c1eab..0cf333a 100644 --- a/docs/PORTING.md +++ b/docs/PORTING.md @@ -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 diff --git a/gradle.properties b/gradle.properties index 41fa8fe..a70b491 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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. diff --git a/src/main/java/net/dnorberg/rivencraft/Rivencraft.java b/src/main/java/net/dnorberg/rivencraft/Rivencraft.java index fc6e911..13123f4 100644 --- a/src/main/java/net/dnorberg/rivencraft/Rivencraft.java +++ b/src/main/java/net/dnorberg/rivencraft/Rivencraft.java @@ -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"); } } diff --git a/src/main/java/net/dnorberg/rivencraft/client/ClientAgeCelestialAngles.java b/src/main/java/net/dnorberg/rivencraft/client/ClientAgeCelestialAngles.java new file mode 100644 index 0000000..0ed47c8 --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/client/ClientAgeCelestialAngles.java @@ -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, 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 dimension) { + return ANGLES.getOrDefault(dimension, 0.0F); + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/event/AgeCelestialAngleSync.java b/src/main/java/net/dnorberg/rivencraft/event/AgeCelestialAngleSync.java new file mode 100644 index 0000000..a89186d --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/event/AgeCelestialAngleSync.java @@ -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())); + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/mixin/client/LevelRendererMixin.java b/src/main/java/net/dnorberg/rivencraft/mixin/client/LevelRendererMixin.java new file mode 100644 index 0000000..514220d --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/mixin/client/LevelRendererMixin.java @@ -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)); + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/network/AgeCelestialAnglePayload.java b/src/main/java/net/dnorberg/rivencraft/network/AgeCelestialAnglePayload.java new file mode 100644 index 0000000..09908ed --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/network/AgeCelestialAnglePayload.java @@ -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 dimension, float angle) + implements CustomPacketPayload { + public static final Type TYPE = new Type<>( + ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "age_celestial_angle") + ); + public static final StreamCodec 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 type() { + return TYPE; + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java b/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java index a4bdefa..76aa1bf 100644 --- a/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java +++ b/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java @@ -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; diff --git a/src/main/resources/rivencraft.mixins.json b/src/main/resources/rivencraft.mixins.json new file mode 100644 index 0000000..5962154 --- /dev/null +++ b/src/main/resources/rivencraft.mixins.json @@ -0,0 +1,12 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "net.dnorberg.rivencraft.mixin", + "compatibilityLevel": "JAVA_21", + "client": [ + "client.LevelRendererMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/main/templates/META-INF/neoforge.mods.toml b/src/main/templates/META-INF/neoforge.mods.toml index 26a4b21..c3c6377 100644 --- a/src/main/templates/META-INF/neoforge.mods.toml +++ b/src/main/templates/META-INF/neoforge.mods.toml @@ -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