From 1586597161b3e4cf15af90f3286e1e226e0a3311 Mon Sep 17 00:00:00 2001 From: Dustin Norberg Date: Tue, 22 Sep 2026 05:58:02 -0400 Subject: [PATCH] Use HUD overlay for book exploration --- gradle.properties | 2 +- .../client/ClientBookExploreOverlay.java | 62 +++++++++++++ .../rivencraft/client/ClientEvents.java | 3 + .../client/ClientLinkPreviewHandler.java | 4 +- ...sivePortalsDestinationPreviewRenderer.java | 11 +++ .../client/screen/DescriptiveBookScreen.java | 88 +++++++++++++++---- .../client/screen/ExplorableBookScreen.java | 19 ++++ .../client/screen/LinkingBookScreen.java | 88 +++++++++++++++---- .../assets/rivencraft/lang/en_us.json | 1 + 9 files changed, 244 insertions(+), 34 deletions(-) create mode 100644 src/main/java/net/dnorberg/rivencraft/client/ClientBookExploreOverlay.java create mode 100644 src/main/java/net/dnorberg/rivencraft/client/screen/ExplorableBookScreen.java diff --git a/gradle.properties b/gradle.properties index 140dc71..416b7db 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.16.8 +mod_version=0.2.16.9 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/client/ClientBookExploreOverlay.java b/src/main/java/net/dnorberg/rivencraft/client/ClientBookExploreOverlay.java new file mode 100644 index 0000000..ca9ce1f --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/client/ClientBookExploreOverlay.java @@ -0,0 +1,62 @@ +package net.dnorberg.rivencraft.client; + +import com.mojang.blaze3d.platform.InputConstants; +import net.dnorberg.rivencraft.client.screen.ExplorableBookScreen; +import net.dnorberg.rivencraft.network.LinkPreviewPayload; +import net.minecraft.client.Minecraft; +import net.neoforged.neoforge.client.event.ClientTickEvent; +import net.neoforged.neoforge.client.event.InputEvent; +import net.neoforged.neoforge.client.event.RenderGuiEvent; +import org.lwjgl.glfw.GLFW; + +public final class ClientBookExploreOverlay { + private static ExplorableBookScreen bookScreen; + + private ClientBookExploreOverlay() { + } + + public static void open(ExplorableBookScreen screen) { + bookScreen = screen; + } + + public static boolean acceptPreview(LinkPreviewPayload payload) { + if (bookScreen == null) { + return false; + } + bookScreen.acceptExplorePreview(payload); + return true; + } + + public static void onClientTick(ClientTickEvent.Post event) { + if (bookScreen == null) { + return; + } + Minecraft minecraft = Minecraft.getInstance(); + if (minecraft.player == null || minecraft.level == null) { + bookScreen.abandonExploreOverlay(); + bookScreen = null; + return; + } + bookScreen.tickExploreOverlay(); + } + + public static void onRenderGui(RenderGuiEvent.Post event) { + if (bookScreen != null && Minecraft.getInstance().screen == null) { + bookScreen.renderExploreOverlay(event.getGuiGraphics(), + event.getPartialTick().getGameTimeDeltaPartialTick(false)); + } + } + + public static void onMouseButton(InputEvent.MouseButton.Pre event) { + if (bookScreen == null + || Minecraft.getInstance().screen != null + || event.getButton() != GLFW.GLFW_MOUSE_BUTTON_RIGHT + || event.getAction() != InputConstants.PRESS) { + return; + } + event.setCanceled(true); + ExplorableBookScreen screen = bookScreen; + bookScreen = null; + screen.returnFromExploreOverlay(); + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java b/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java index cbb0638..46a3cc6 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java +++ b/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java @@ -23,6 +23,9 @@ public final class ClientEvents { public static void clientSetup(FMLClientSetupEvent event) { NeoForge.EVENT_BUS.addListener(ClientBookInteractionHandler::onRightClickItem); NeoForge.EVENT_BUS.addListener(ClientBookInteractionHandler::onRightClickBlock); + NeoForge.EVENT_BUS.addListener(ClientBookExploreOverlay::onClientTick); + NeoForge.EVENT_BUS.addListener(ClientBookExploreOverlay::onRenderGui); + NeoForge.EVENT_BUS.addListener(ClientBookExploreOverlay::onMouseButton); } } diff --git a/src/main/java/net/dnorberg/rivencraft/client/ClientLinkPreviewHandler.java b/src/main/java/net/dnorberg/rivencraft/client/ClientLinkPreviewHandler.java index c9f1f8f..a5d9669 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/ClientLinkPreviewHandler.java +++ b/src/main/java/net/dnorberg/rivencraft/client/ClientLinkPreviewHandler.java @@ -12,7 +12,9 @@ public final class ClientLinkPreviewHandler { public static void handle(LinkPreviewPayload payload, IPayloadContext context) { context.enqueueWork(() -> { - if (Minecraft.getInstance().screen instanceof LinkingBookScreen screen) { + if (ClientBookExploreOverlay.acceptPreview(payload)) { + return; + } else if (Minecraft.getInstance().screen instanceof LinkingBookScreen screen) { screen.acceptPreview(payload); } else if (Minecraft.getInstance().screen instanceof DescriptiveBookScreen screen) { screen.acceptPreview(payload); diff --git a/src/main/java/net/dnorberg/rivencraft/client/preview/ImmersivePortalsDestinationPreviewRenderer.java b/src/main/java/net/dnorberg/rivencraft/client/preview/ImmersivePortalsDestinationPreviewRenderer.java index 25417da..c2ce8a6 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/preview/ImmersivePortalsDestinationPreviewRenderer.java +++ b/src/main/java/net/dnorberg/rivencraft/client/preview/ImmersivePortalsDestinationPreviewRenderer.java @@ -26,6 +26,10 @@ public final class ImmersivePortalsDestinationPreviewRenderer implements Destina private Vec3 cameraPosition; private Vec3 explorationPlayerOrigin; private Vec3 explorationCameraOrigin; + private float explorationPlayerYaw; + private float explorationPlayerPitch; + private float explorationCameraYaw; + private float explorationCameraPitch; private float yaw; private float pitch; private int ticks; @@ -52,6 +56,9 @@ public final class ImmersivePortalsDestinationPreviewRenderer implements Destina } if (minecraft.player != null && explorationPlayerOrigin != null && explorationCameraOrigin != null) { cameraPosition = explorationCameraOrigin.add(minecraft.player.position().subtract(explorationPlayerOrigin)); + yaw = explorationCameraYaw + Mth.wrapDegrees(minecraft.player.getYRot() - explorationPlayerYaw); + pitch = Mth.clamp(explorationCameraPitch + + minecraft.player.getXRot() - explorationPlayerPitch, -89.0F, 89.0F); } } @@ -127,6 +134,10 @@ public final class ImmersivePortalsDestinationPreviewRenderer implements Destina if (exploring && minecraft.player != null) { explorationPlayerOrigin = minecraft.player.position(); explorationCameraOrigin = cameraPosition; + explorationPlayerYaw = minecraft.player.getYRot(); + explorationPlayerPitch = minecraft.player.getXRot(); + explorationCameraYaw = yaw; + explorationCameraPitch = pitch; } else { clearMovement(); explorationPlayerOrigin = null; diff --git a/src/main/java/net/dnorberg/rivencraft/client/screen/DescriptiveBookScreen.java b/src/main/java/net/dnorberg/rivencraft/client/screen/DescriptiveBookScreen.java index d5c6aeb..e5b992b 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/screen/DescriptiveBookScreen.java +++ b/src/main/java/net/dnorberg/rivencraft/client/screen/DescriptiveBookScreen.java @@ -1,6 +1,7 @@ package net.dnorberg.rivencraft.client.screen; import net.dnorberg.rivencraft.book.AgeReference; +import net.dnorberg.rivencraft.client.ClientBookExploreOverlay; import net.dnorberg.rivencraft.network.UseDescriptiveBookPayload; import net.dnorberg.rivencraft.network.BindDescriptiveBookPayload; import net.dnorberg.rivencraft.network.ModNetworking; @@ -22,7 +23,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.InteractionHand; import net.neoforged.neoforge.network.PacketDistributor; -public final class DescriptiveBookScreen extends Screen { +public final class DescriptiveBookScreen extends Screen implements ExplorableBookScreen { private static final int BOOK_TEXT_COLOR = 0xFF3B281B; private static final ResourceLocation BOOK_TEXTURE = ResourceLocation.fromNamespaceAndPath( "rivencraft", "textures/gui/linking_book.png" @@ -43,6 +44,7 @@ public final class DescriptiveBookScreen extends Screen { private DestinationPreviewRenderer preview; private EditBox titleInput; private Button exploreButton; + private boolean movingToExploreOverlay; public DescriptiveBookScreen(InteractionHand hand, ItemStack book) { super(book.getHoverName()); @@ -79,19 +81,24 @@ public final class DescriptiveBookScreen extends Screen { .bounds(rightPage + 65, top + 100, 59, 20) .build()); } else { - preview = DestinationPreviewRenderers.create(); + boolean requestPreview = preview == null; + if (requestPreview) { + preview = DestinationPreviewRenderers.create(); + } if (preview.supportsExploration()) { exploreButton = addRenderableWidget(Button.builder( - Component.translatable("screen.rivencraft.book.explore"), button -> toggleExploration()) + Component.translatable("screen.rivencraft.book.explore"), button -> enterExploreOverlay()) .bounds(left + 61, top + 145, 60, 20).build()); } - PacketDistributor.sendToServer(new RequestLinkPreviewPayload( - true, - bookStandPos != null, - bookStandPos == null && hand == InteractionHand.OFF_HAND, - bookStandPos == null ? BlockPos.ZERO : bookStandPos, - preview.supportsExploration() - )); + if (requestPreview) { + PacketDistributor.sendToServer(new RequestLinkPreviewPayload( + true, + bookStandPos != null, + bookStandPos == null && hand == InteractionHand.OFF_HAND, + bookStandPos == null ? BlockPos.ZERO : bookStandPos, + preview.supportsExploration() + )); + } } } @@ -114,6 +121,11 @@ public final class DescriptiveBookScreen extends Screen { @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { + renderBook(graphics, partialTick); + super.render(graphics, mouseX, mouseY, partialTick); + } + + private void renderBook(GuiGraphics graphics, float partialTick) { int left = (width - BOOK_WIDTH) / 2; int top = (height - BOOK_HEIGHT) / 2; graphics.blit(BOOK_TEXTURE, left, top, BOOK_WIDTH, BOOK_HEIGHT, 0, 0, @@ -135,16 +147,16 @@ public final class DescriptiveBookScreen extends Screen { } } - super.render(graphics, mouseX, mouseY, partialTick); } @Override public void removed() { - if (preview != null) { + if (preview != null && !movingToExploreOverlay) { if (preview.supportsExploration()) { PacketDistributor.sendToServer(new ReleaseLinkPreviewPayload()); } preview.close(); + preview = null; } } @@ -193,10 +205,11 @@ public final class DescriptiveBookScreen extends Screen { || super.keyReleased(keyCode, scanCode, modifiers); } - private void toggleExploration() { - preview.setExploring(!preview.isExploring()); - exploreButton.setMessage(Component.translatable(preview.isExploring() - ? "screen.rivencraft.book.exploring" : "screen.rivencraft.book.explore")); + private void enterExploreOverlay() { + preview.setExploring(true); + movingToExploreOverlay = true; + ClientBookExploreOverlay.open(this); + minecraft.setScreen(null); } private void resetExploration() { @@ -204,6 +217,49 @@ public final class DescriptiveBookScreen extends Screen { exploreButton.setMessage(Component.translatable("screen.rivencraft.book.explore")); } + @Override + public Screen asScreen() { + return this; + } + + @Override + public void tickExploreOverlay() { + preview.tick(); + } + + @Override + public void renderExploreOverlay(GuiGraphics graphics, float partialTick) { + width = minecraft.getWindow().getGuiScaledWidth(); + height = minecraft.getWindow().getGuiScaledHeight(); + renderBook(graphics, partialTick); + int left = (width - BOOK_WIDTH) / 2; + int top = (height - BOOK_HEIGHT) / 2; + graphics.drawCenteredString(font, + Component.translatable("screen.rivencraft.book.return_from_explore"), + left + 91, top + 154, BOOK_TEXT_COLOR); + } + + @Override + public void returnFromExploreOverlay() { + movingToExploreOverlay = false; + preview.resetView(); + minecraft.setScreen(this); + } + + @Override + public void abandonExploreOverlay() { + movingToExploreOverlay = false; + if (preview != null) { + preview.close(); + preview = null; + } + } + + @Override + public void acceptExplorePreview(LinkPreviewPayload payload) { + acceptPreview(payload); + } + private void link() { PacketDistributor.sendToServer(new UseDescriptiveBookPayload( bookStandPos != null, diff --git a/src/main/java/net/dnorberg/rivencraft/client/screen/ExplorableBookScreen.java b/src/main/java/net/dnorberg/rivencraft/client/screen/ExplorableBookScreen.java new file mode 100644 index 0000000..3ce1492 --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/client/screen/ExplorableBookScreen.java @@ -0,0 +1,19 @@ +package net.dnorberg.rivencraft.client.screen; + +import net.dnorberg.rivencraft.network.LinkPreviewPayload; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; + +public interface ExplorableBookScreen { + Screen asScreen(); + + void tickExploreOverlay(); + + void renderExploreOverlay(GuiGraphics graphics, float partialTick); + + void returnFromExploreOverlay(); + + void abandonExploreOverlay(); + + void acceptExplorePreview(LinkPreviewPayload payload); +} diff --git a/src/main/java/net/dnorberg/rivencraft/client/screen/LinkingBookScreen.java b/src/main/java/net/dnorberg/rivencraft/client/screen/LinkingBookScreen.java index 541147e..d5a4153 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/screen/LinkingBookScreen.java +++ b/src/main/java/net/dnorberg/rivencraft/client/screen/LinkingBookScreen.java @@ -1,6 +1,7 @@ package net.dnorberg.rivencraft.client.screen; import net.dnorberg.rivencraft.book.LinkTarget; +import net.dnorberg.rivencraft.client.ClientBookExploreOverlay; import net.dnorberg.rivencraft.client.preview.DestinationPreviewRenderer; import net.dnorberg.rivencraft.client.preview.DestinationPreviewRenderers; import net.dnorberg.rivencraft.network.BindLinkingBookPayload; @@ -24,7 +25,7 @@ import net.minecraft.world.InteractionHand; import net.minecraft.world.item.ItemStack; import net.neoforged.neoforge.network.PacketDistributor; -public final class LinkingBookScreen extends Screen { +public final class LinkingBookScreen extends Screen implements ExplorableBookScreen { private static final int BOOK_TEXT_COLOR = 0xFF3B281B; private static final ResourceLocation BOOK_TEXTURE = ResourceLocation.fromNamespaceAndPath( "rivencraft", "textures/gui/linking_book.png" @@ -45,6 +46,7 @@ public final class LinkingBookScreen extends Screen { private DestinationPreviewRenderer preview; private EditBox titleInput; private Button exploreButton; + private boolean movingToExploreOverlay; public LinkingBookScreen(InteractionHand hand, ItemStack book) { super(book.getHoverName()); @@ -81,19 +83,24 @@ public final class LinkingBookScreen extends Screen { .bounds(rightPage + 65, top + 100, 59, 20) .build()); } else { - preview = DestinationPreviewRenderers.create(); + boolean requestPreview = preview == null; + if (requestPreview) { + preview = DestinationPreviewRenderers.create(); + } if (preview.supportsExploration()) { exploreButton = addRenderableWidget(Button.builder( - Component.translatable("screen.rivencraft.book.explore"), button -> toggleExploration()) + Component.translatable("screen.rivencraft.book.explore"), button -> enterExploreOverlay()) .bounds(left + 61, top + 145, 60, 20).build()); } - PacketDistributor.sendToServer(new RequestLinkPreviewPayload( - false, - bookStandPos != null, - bookStandPos == null && hand == InteractionHand.OFF_HAND, - bookStandPos == null ? BlockPos.ZERO : bookStandPos, - preview.supportsExploration() - )); + if (requestPreview) { + PacketDistributor.sendToServer(new RequestLinkPreviewPayload( + false, + bookStandPos != null, + bookStandPos == null && hand == InteractionHand.OFF_HAND, + bookStandPos == null ? BlockPos.ZERO : bookStandPos, + preview.supportsExploration() + )); + } } } @@ -117,6 +124,11 @@ public final class LinkingBookScreen extends Screen { @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { + renderBook(graphics, partialTick); + super.render(graphics, mouseX, mouseY, partialTick); + } + + private void renderBook(GuiGraphics graphics, float partialTick) { int left = (width - BOOK_WIDTH) / 2; int top = (height - BOOK_HEIGHT) / 2; graphics.blit(BOOK_TEXTURE, left, top, BOOK_WIDTH, BOOK_HEIGHT, 0, 0, @@ -141,7 +153,6 @@ public final class LinkingBookScreen extends Screen { Component.translatable("screen.rivencraft.book.click_to_link"), rightCenter, top + 119); } - super.render(graphics, mouseX, mouseY, partialTick); } @Override @@ -164,11 +175,12 @@ public final class LinkingBookScreen extends Screen { @Override public void removed() { - if (preview != null) { + if (preview != null && !movingToExploreOverlay) { if (preview.supportsExploration()) { PacketDistributor.sendToServer(new ReleaseLinkPreviewPayload()); } preview.close(); + preview = null; } } @@ -193,10 +205,11 @@ public final class LinkingBookScreen extends Screen { || super.keyReleased(keyCode, scanCode, modifiers); } - private void toggleExploration() { - preview.setExploring(!preview.isExploring()); - exploreButton.setMessage(Component.translatable(preview.isExploring() - ? "screen.rivencraft.book.exploring" : "screen.rivencraft.book.explore")); + private void enterExploreOverlay() { + preview.setExploring(true); + movingToExploreOverlay = true; + ClientBookExploreOverlay.open(this); + minecraft.setScreen(null); } private void resetExploration() { @@ -210,6 +223,49 @@ public final class LinkingBookScreen extends Screen { } } + @Override + public Screen asScreen() { + return this; + } + + @Override + public void tickExploreOverlay() { + preview.tick(); + } + + @Override + public void renderExploreOverlay(GuiGraphics graphics, float partialTick) { + width = minecraft.getWindow().getGuiScaledWidth(); + height = minecraft.getWindow().getGuiScaledHeight(); + renderBook(graphics, partialTick); + int left = (width - BOOK_WIDTH) / 2; + int top = (height - BOOK_HEIGHT) / 2; + drawCenteredWithoutShadow(graphics, + Component.translatable("screen.rivencraft.book.return_from_explore"), + left + 91, top + 154); + } + + @Override + public void returnFromExploreOverlay() { + movingToExploreOverlay = false; + preview.resetView(); + minecraft.setScreen(this); + } + + @Override + public void abandonExploreOverlay() { + movingToExploreOverlay = false; + if (preview != null) { + preview.close(); + preview = null; + } + } + + @Override + public void acceptExplorePreview(LinkPreviewPayload payload) { + acceptPreview(payload); + } + private void bind() { String name = titleInput.getValue().strip(); if (!name.isEmpty()) { diff --git a/src/main/resources/assets/rivencraft/lang/en_us.json b/src/main/resources/assets/rivencraft/lang/en_us.json index 748df21..bab7d2b 100644 --- a/src/main/resources/assets/rivencraft/lang/en_us.json +++ b/src/main/resources/assets/rivencraft/lang/en_us.json @@ -40,5 +40,6 @@ "screen.rivencraft.book.click_to_link": "Click the panel to link", "screen.rivencraft.book.explore": "Explore", "screen.rivencraft.book.exploring": "Exploring", + "screen.rivencraft.book.return_from_explore": "Right-click to return", "itemGroup.rivencraft": "Rivencraft" }