diff --git a/README.md b/README.md index 1174de6..7279fed 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@ right-clicking with an empty hand. Sneak-right-click with an empty hand to take the book back and restore the vanilla lectern. Breaking a Book Stand drops both the stored book and a vanilla lectern. -Version 0.1.4 establishes a Ponder-backed destination-preview boundary and an -animated loading view. Transmitting a destination snapshot and rendering its -actual blocks as the rotating scene is the next preview iteration. +Bound Linking Books request a small, server-validated snapshot around their +destination and render its blocks as a rotating Ponder scene on the left page. +The snapshot is visual only; the server independently validates every link. ## Requirements diff --git a/docs/PORTING.md b/docs/PORTING.md index 750407a..307c9c3 100644 --- a/docs/PORTING.md +++ b/docs/PORTING.md @@ -51,7 +51,7 @@ the symbol grammar is introduced. - [x] Book-screen foundation with server-authoritative naming, binding, and explicit link activation. - [x] Ponder dependency and isolated destination-preview level boundary. -- [ ] Server-provided destination snapshots rendered as rotating Ponder scenes. +- [x] Server-provided destination snapshots rendered as rotating Ponder scenes. - [x] Book Stand floating names synchronized from the stored book stack. - [x] Open the stored book screen from a Book Stand with server-validated linking. - [ ] Automated in-game tests for binding, persistence, and travel. @@ -110,6 +110,10 @@ revalidates the stand position, interaction distance, stored item, and target. Version 0.1.12 replaces the Linking Book screen's convenience text draws with explicit no-shadow rendering for clearer dark ink on the parchment texture. +Version 0.1.13 requests a bounded, server-validated 9-by-7-by-9 block snapshot +from the held book or nearby Book Stand and renders it as a clipped, rotating +Ponder world section on the Linking Book's left page. + The preview code is separated behind a renderer interface and uses an isolated Ponder level. The first iteration supplies the animated loading boundary; the next iteration will add server-authoritative snapshot transport and populate diff --git a/gradle.properties b/gradle.properties index 4a42a22..e0912c5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -33,7 +33,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. See https://semver.org/ -mod_version=0.1.12 +mod_version=0.1.13 # 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. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java b/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java index 8f28f7f..cbb0638 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java +++ b/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java @@ -24,4 +24,5 @@ public final class ClientEvents { NeoForge.EVENT_BUS.addListener(ClientBookInteractionHandler::onRightClickItem); NeoForge.EVENT_BUS.addListener(ClientBookInteractionHandler::onRightClickBlock); } + } diff --git a/src/main/java/net/dnorberg/rivencraft/client/ClientLinkPreviewHandler.java b/src/main/java/net/dnorberg/rivencraft/client/ClientLinkPreviewHandler.java new file mode 100644 index 0000000..484f9d9 --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/client/ClientLinkPreviewHandler.java @@ -0,0 +1,19 @@ +package net.dnorberg.rivencraft.client; + +import net.dnorberg.rivencraft.client.screen.LinkingBookScreen; +import net.dnorberg.rivencraft.network.LinkPreviewPayload; +import net.minecraft.client.Minecraft; +import net.neoforged.neoforge.network.handling.IPayloadContext; + +public final class ClientLinkPreviewHandler { + private ClientLinkPreviewHandler() { + } + + public static void handle(LinkPreviewPayload payload, IPayloadContext context) { + context.enqueueWork(() -> { + if (Minecraft.getInstance().screen instanceof LinkingBookScreen screen) { + screen.acceptPreview(payload); + } + }); + } +} diff --git a/src/main/java/net/dnorberg/rivencraft/client/preview/DestinationPreviewRenderer.java b/src/main/java/net/dnorberg/rivencraft/client/preview/DestinationPreviewRenderer.java index 267c3c9..8f13e16 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/preview/DestinationPreviewRenderer.java +++ b/src/main/java/net/dnorberg/rivencraft/client/preview/DestinationPreviewRenderer.java @@ -1,8 +1,11 @@ package net.dnorberg.rivencraft.client.preview; import net.minecraft.client.gui.GuiGraphics; +import net.dnorberg.rivencraft.network.LinkPreviewPayload; public interface DestinationPreviewRenderer extends AutoCloseable { + void accept(LinkPreviewPayload payload); + void tick(); void render(GuiGraphics graphics, int left, int top, int width, int height, float partialTick); diff --git a/src/main/java/net/dnorberg/rivencraft/client/preview/PonderDestinationPreviewRenderer.java b/src/main/java/net/dnorberg/rivencraft/client/preview/PonderDestinationPreviewRenderer.java index 9178c1a..ad37bfa 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/preview/PonderDestinationPreviewRenderer.java +++ b/src/main/java/net/dnorberg/rivencraft/client/preview/PonderDestinationPreviewRenderer.java @@ -1,10 +1,27 @@ package net.dnorberg.rivencraft.client.preview; +import com.mojang.blaze3d.systems.RenderSystem; import net.createmod.ponder.api.level.PonderLevel; +import net.createmod.ponder.api.element.PonderSceneElement; +import net.createmod.ponder.api.scene.Selection; +import net.createmod.ponder.foundation.SelectionImpl; +import net.createmod.ponder.foundation.element.WorldSectionElementImpl; +import net.createmod.catnip.gui.UIRenderHelper; +import net.createmod.catnip.render.DefaultSuperRenderTypeBuffer; +import net.createmod.catnip.render.SuperRenderTypeBuffer; +import net.dnorberg.rivencraft.network.LinkPreviewPayload; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.renderer.RenderType; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.levelgen.structure.BoundingBox; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexSorting; +import com.mojang.math.Axis; +import org.joml.Matrix4f; +import org.joml.Vector3f; /** * Initial Ponder-backed preview boundary. @@ -14,12 +31,27 @@ import net.minecraft.network.chat.Component; * state rather than reading arbitrary remote chunks from the client.
*/ public final class PonderDestinationPreviewRenderer implements DestinationPreviewRenderer { + private static final Vector3f DIFFUSE_LIGHT_0 = new Vector3f(0.4F, -1.0F, 0.7F).normalize(); + private static final Vector3f DIFFUSE_LIGHT_1 = new Vector3f(-0.4F, -0.5F, 0.7F).normalize(); private final PonderLevel previewLevel; + private final Selection selection; + private final PonderSceneElement section; private int ticks; + private boolean loaded; public PonderDestinationPreviewRenderer() { Minecraft minecraft = Minecraft.getInstance(); previewLevel = new PonderLevel(BlockPos.ZERO, minecraft.level); + selection = SelectionImpl.of(new BoundingBox(0, 0, 0, 8, 6, 8)); + section = new WorldSectionElementImpl(selection); + } + + @Override + public void accept(LinkPreviewPayload payload) { + selection.forEach(pos -> previewLevel.setBlock(pos, Blocks.AIR.defaultBlockState(), 2)); + payload.blocks().forEach(block -> previewLevel.setBlock(block.pos(), block.state(), 2)); + ((net.createmod.ponder.api.element.WorldSectionElement)section).queueRedraw(); + loaded = true; } @Override @@ -33,6 +65,11 @@ public final class PonderDestinationPreviewRenderer implements DestinationPrevie graphics.fill(left, top, left + width, top + height, 0xFF080C18); graphics.renderOutline(left, top, width, height, 0xFF7B6AA8); + if (loaded) { + renderSnapshot(graphics, left, top, width, height, partialTick); + return; + } + double angle = (ticks + partialTick) * 0.04; int centerX = left + width / 2; int centerY = top + height / 2; @@ -47,4 +84,38 @@ public final class PonderDestinationPreviewRenderer implements DestinationPrevie 0xFFB8C5E0 ); } + + private void renderSnapshot(GuiGraphics graphics, int left, int top, int width, int height, float partialTick) { + graphics.enableScissor(left + 1, top + 1, left + width - 1, top + height - 1); + RenderSystem.enableBlend(); + RenderSystem.enableDepthTest(); + RenderSystem.backupProjectionMatrix(); + RenderSystem.setupLevelDiffuseLighting(DIFFUSE_LIGHT_0, DIFFUSE_LIGHT_1); + + Matrix4f projection = new Matrix4f(RenderSystem.getProjectionMatrix()); + projection.translate(0.0F, 0.0F, 800.0F); + RenderSystem.setProjectionMatrix(projection, VertexSorting.DISTANCE_TO_ORIGIN); + + PoseStack poseStack = graphics.pose(); + poseStack.pushPose(); + poseStack.translate(0.0F, 0.0F, -800.0F); + poseStack.translate(left + width / 2.0F, top + height / 2.0F + 8.0F, 200.0F); + poseStack.mulPose(Axis.XP.rotationDegrees(-28.0F)); + poseStack.mulPose(Axis.YP.rotationDegrees((ticks + partialTick) * 0.6F)); + UIRenderHelper.flipForGuiRender(poseStack); + poseStack.scale(10.0F, 10.0F, 10.0F); + poseStack.translate(-4.5F, -1.5F, -4.5F); + + SuperRenderTypeBuffer buffer = DefaultSuperRenderTypeBuffer.getInstance(); + section.renderFirst(previewLevel, buffer, graphics, partialTick); + for (RenderType type : RenderType.chunkBufferLayers()) { + section.renderLayer(previewLevel, buffer, type, graphics, partialTick); + } + section.renderLast(previewLevel, buffer, graphics, partialTick); + buffer.draw(); + + poseStack.popPose(); + RenderSystem.restoreProjectionMatrix(); + graphics.disableScissor(); + } } 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 fcd5f3a..34a55f5 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/screen/LinkingBookScreen.java +++ b/src/main/java/net/dnorberg/rivencraft/client/screen/LinkingBookScreen.java @@ -7,6 +7,8 @@ import net.dnorberg.rivencraft.network.BindLinkingBookPayload; import net.dnorberg.rivencraft.network.ModNetworking; import net.dnorberg.rivencraft.network.UseLinkingBookPayload; import net.dnorberg.rivencraft.network.UseBookStandPayload; +import net.dnorberg.rivencraft.network.RequestLinkPreviewPayload; +import net.dnorberg.rivencraft.network.LinkPreviewPayload; import net.dnorberg.rivencraft.registry.ModDataComponents; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; @@ -77,6 +79,11 @@ public final class LinkingBookScreen extends Screen { .build()); } else { preview = new PonderDestinationPreviewRenderer(); + PacketDistributor.sendToServer(new RequestLinkPreviewPayload( + bookStandPos != null, + bookStandPos == null && hand == InteractionHand.OFF_HAND, + bookStandPos == null ? BlockPos.ZERO : bookStandPos + )); } } @@ -143,6 +150,12 @@ public final class LinkingBookScreen extends Screen { } } + public void acceptPreview(LinkPreviewPayload payload) { + if (preview != null) { + preview.accept(payload); + } + } + private void bind() { String name = titleInput.getValue().strip(); if (!name.isEmpty()) { diff --git a/src/main/java/net/dnorberg/rivencraft/network/LinkPreviewPayload.java b/src/main/java/net/dnorberg/rivencraft/network/LinkPreviewPayload.java new file mode 100644 index 0000000..ed8eea6 --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/network/LinkPreviewPayload.java @@ -0,0 +1,47 @@ +package net.dnorberg.rivencraft.network; + +import java.util.ArrayList; +import java.util.List; +import net.dnorberg.rivencraft.Rivencraft; +import net.minecraft.core.BlockPos; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +public record LinkPreviewPayload(List