diff --git a/docs/PORTING.md b/docs/PORTING.md index dc73134..32db7de 100644 --- a/docs/PORTING.md +++ b/docs/PORTING.md @@ -152,6 +152,10 @@ dimensions and create a bound Return Link to the source on successful travel. Version 0.2.1 gives Descriptive Books the same open-parchment layout and left-page link interaction as Linking Books. +Version 0.2.2 adds the same server-provided Ponder destination preview to +Descriptive Books. If no snapshot is available, the left page shows an +animated linking-panel fallback. + The preview code is separated behind a renderer interface and uses an isolated Ponder level populated from server-authoritative destination snapshots. diff --git a/gradle.properties b/gradle.properties index 7ed9195..111aaeb 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.2.1 +mod_version=0.2.2 # 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/ClientLinkPreviewHandler.java b/src/main/java/net/dnorberg/rivencraft/client/ClientLinkPreviewHandler.java index 484f9d9..c9f1f8f 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/ClientLinkPreviewHandler.java +++ b/src/main/java/net/dnorberg/rivencraft/client/ClientLinkPreviewHandler.java @@ -1,5 +1,6 @@ package net.dnorberg.rivencraft.client; +import net.dnorberg.rivencraft.client.screen.DescriptiveBookScreen; import net.dnorberg.rivencraft.client.screen.LinkingBookScreen; import net.dnorberg.rivencraft.network.LinkPreviewPayload; import net.minecraft.client.Minecraft; @@ -13,6 +14,8 @@ public final class ClientLinkPreviewHandler { context.enqueueWork(() -> { 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/PonderDestinationPreviewRenderer.java b/src/main/java/net/dnorberg/rivencraft/client/preview/PonderDestinationPreviewRenderer.java index 17e482e..f0e8767 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/preview/PonderDestinationPreviewRenderer.java +++ b/src/main/java/net/dnorberg/rivencraft/client/preview/PonderDestinationPreviewRenderer.java @@ -14,7 +14,6 @@ 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; @@ -57,10 +56,15 @@ public final class PonderDestinationPreviewRenderer implements DestinationPrevie @Override public void accept(LinkPreviewPayload payload) { + backgroundColor = 0xFF000000 | payload.skyColor() & 0x00FFFFFF; + if (payload.blocks().isEmpty()) { + loaded = false; + return; + } + snapshotSelection.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(); - backgroundColor = 0xFF000000 | payload.skyColor() & 0x00FFFFFF; loaded = true; } @@ -80,19 +84,19 @@ public final class PonderDestinationPreviewRenderer implements DestinationPrevie return; } - double angle = (ticks + partialTick) * 0.04; + // A destination without snapshot blocks uses a self-contained animated + // linking panel instead of exposing an empty Ponder level. + double angle = (ticks + partialTick) * 0.08; int centerX = left + width / 2; int centerY = top + height / 2; - int orbitX = centerX + (int)(Math.cos(angle) * Math.max(8, width / 4)); - int orbitY = centerY + (int)(Math.sin(angle) * Math.max(4, height / 8)); - graphics.fill(orbitX - 2, orbitY - 2, orbitX + 3, orbitY + 3, 0xFF8ED6FF); - graphics.drawCenteredString( - Minecraft.getInstance().font, - Component.translatable("screen.rivencraft.book.preview_pending"), - centerX, - centerY - 4, - 0xFFB8C5E0 - ); + int radius = Math.max(8, Math.min(width, height) / 3); + for (int ring = 0; ring < 3; ring++) { + int ringRadius = radius - ring * 5; + int x = centerX + (int)(Math.cos(angle + ring * 2.1D) * ringRadius); + int y = centerY + (int)(Math.sin(angle + ring * 2.1D) * ringRadius * 0.55D); + graphics.fill(x - 2, y - 2, x + 3, y + 3, ring == 0 ? 0xFFB9ECFF : 0xFF6B9DDC); + } + graphics.fill(centerX - 3, centerY - 3, centerX + 4, centerY + 4, 0xFFE5F5FF); } private void renderSnapshot(GuiGraphics graphics, int left, int top, int width, int height, float partialTick) { 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 d1696db..328a724 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/screen/DescriptiveBookScreen.java +++ b/src/main/java/net/dnorberg/rivencraft/client/screen/DescriptiveBookScreen.java @@ -2,7 +2,11 @@ package net.dnorberg.rivencraft.client.screen; import net.dnorberg.rivencraft.book.AgeReference; import net.dnorberg.rivencraft.network.UseDescriptiveBookPayload; +import net.dnorberg.rivencraft.network.RequestLinkPreviewPayload; +import net.dnorberg.rivencraft.network.LinkPreviewPayload; import net.dnorberg.rivencraft.registry.ModDataComponents; +import net.dnorberg.rivencraft.client.preview.DestinationPreviewRenderer; +import net.dnorberg.rivencraft.client.preview.PonderDestinationPreviewRenderer; import net.minecraft.core.BlockPos; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.Screen; @@ -30,6 +34,7 @@ public final class DescriptiveBookScreen extends Screen { private final AgeReference age; private final InteractionHand hand; private final BlockPos bookStandPos; + private DestinationPreviewRenderer preview; public DescriptiveBookScreen(InteractionHand hand, ItemStack book) { super(book.getHoverName()); @@ -45,6 +50,26 @@ public final class DescriptiveBookScreen extends Screen { this.bookStandPos = bookStandPos.immutable(); } + @Override + protected void init() { + if (age != null) { + preview = new PonderDestinationPreviewRenderer(); + PacketDistributor.sendToServer(new RequestLinkPreviewPayload( + true, + bookStandPos != null, + bookStandPos == null && hand == InteractionHand.OFF_HAND, + bookStandPos == null ? BlockPos.ZERO : bookStandPos + )); + } + } + + @Override + public void tick() { + if (preview != null) { + preview.tick(); + } + } + @Override public void renderBackground(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { // Like Linking Books, the parchment is the full screen background. @@ -61,21 +86,26 @@ public final class DescriptiveBookScreen extends Screen { drawWrappedWithoutShadow(graphics, Component.translatable("screen.rivencraft.book.unwritten"), left + 177, top + 45, 124); } else { - int leftCenter = left + 91; - int rightCenter = left + 239; - drawCenteredWithoutShadow(graphics, Component.translatable("screen.rivencraft.book.age_link"), - leftCenter, top + 50); - drawWrappedCenteredWithoutShadow(graphics, Component.literal(age.name()), leftCenter, top + 68, 112); - drawCenteredWithoutShadow(graphics, Component.literal(age.shortId()), leftCenter, top + 104); - drawWrappedWithoutShadow(graphics, title, left + 177, top + 43, 124); - drawCenteredWithoutShadow(graphics, Component.literal(age.id().toString()), rightCenter, top + 82); - drawCenteredWithoutShadow(graphics, Component.translatable("screen.rivencraft.book.click_to_link"), - rightCenter, top + 119); + preview.render(graphics, left + LINK_PANEL_X, top + LINK_PANEL_Y, LINK_PANEL_WIDTH, LINK_PANEL_HEIGHT, partialTick); + drawWrappedWithoutShadow(graphics, Component.literal(age.name()), left + 177, top + 43, 124); } super.render(graphics, mouseX, mouseY, partialTick); } + @Override + public void removed() { + if (preview != null) { + preview.close(); + } + } + + public void acceptPreview(LinkPreviewPayload payload) { + if (preview != null) { + preview.accept(payload); + } + } + @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (age != null && button == 0) { @@ -99,10 +129,6 @@ public final class DescriptiveBookScreen extends Screen { onClose(); } - private void drawCenteredWithoutShadow(GuiGraphics graphics, Component text, int centerX, int y) { - graphics.drawString(font, text, centerX - font.width(text) / 2, y, BOOK_TEXT_COLOR, false); - } - private void drawWrappedWithoutShadow(GuiGraphics graphics, Component text, int x, int y, int maxWidth) { for (FormattedCharSequence line : font.split(text, maxWidth)) { graphics.drawString(font, line, x, y, BOOK_TEXT_COLOR, false); @@ -110,10 +136,4 @@ public final class DescriptiveBookScreen extends Screen { } } - private void drawWrappedCenteredWithoutShadow(GuiGraphics graphics, Component text, int centerX, int y, int maxWidth) { - for (FormattedCharSequence line : font.split(text, maxWidth)) { - graphics.drawString(font, line, centerX - font.width(line) / 2, y, BOOK_TEXT_COLOR, false); - y += font.lineHeight; - } - } } 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 34a55f5..7e9f2e6 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/screen/LinkingBookScreen.java +++ b/src/main/java/net/dnorberg/rivencraft/client/screen/LinkingBookScreen.java @@ -80,6 +80,7 @@ public final class LinkingBookScreen extends Screen { } else { preview = new PonderDestinationPreviewRenderer(); PacketDistributor.sendToServer(new RequestLinkPreviewPayload( + false, bookStandPos != null, bookStandPos == null && hand == InteractionHand.OFF_HAND, bookStandPos == null ? BlockPos.ZERO : bookStandPos diff --git a/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java b/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java index 9e7013e..4cd1f32 100644 --- a/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java +++ b/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java @@ -1,6 +1,8 @@ package net.dnorberg.rivencraft.network; import net.dnorberg.rivencraft.book.LinkTarget; +import net.dnorberg.rivencraft.book.AgeReference; +import net.dnorberg.rivencraft.book.VanillaAgeResolver; import net.dnorberg.rivencraft.block.entity.BookStandBlockEntity; import net.dnorberg.rivencraft.client.ClientLinkPreviewHandler; import net.dnorberg.rivencraft.item.DescriptiveBookItem; @@ -31,7 +33,7 @@ public final class ModNetworking { } public static void register(RegisterPayloadHandlersEvent event) { - PayloadRegistrar registrar = event.registrar("4"); + PayloadRegistrar registrar = event.registrar("5"); registrar.playToServer(BindLinkingBookPayload.TYPE, BindLinkingBookPayload.STREAM_CODEC, ModNetworking::bindBook); registrar.playToServer(UseLinkingBookPayload.TYPE, UseLinkingBookPayload.STREAM_CODEC, ModNetworking::useBook); registrar.playToServer(UseDescriptiveBookPayload.TYPE, UseDescriptiveBookPayload.STREAM_CODEC, @@ -125,8 +127,15 @@ public final class ModNetworking { stack = player.getItemInHand(hand(payload.offhand())); } - LinkTarget target = stack.is(ModItems.LINKING_BOOK.get()) - ? stack.get(ModDataComponents.LINK_TARGET.get()) : null; + LinkTarget target; + if (payload.descriptiveBook() && stack.is(ModItems.DESCRIPTIVE_BOOK.get())) { + AgeReference age = stack.get(ModDataComponents.AGE_REFERENCE.get()); + target = age == null ? null : VanillaAgeResolver.resolve(player, age).orElse(null); + } else if (!payload.descriptiveBook() && stack.is(ModItems.LINKING_BOOK.get())) { + target = stack.get(ModDataComponents.LINK_TARGET.get()); + } else { + target = null; + } if (target == null) { return; } diff --git a/src/main/java/net/dnorberg/rivencraft/network/RequestLinkPreviewPayload.java b/src/main/java/net/dnorberg/rivencraft/network/RequestLinkPreviewPayload.java index 9befab5..402163e 100644 --- a/src/main/java/net/dnorberg/rivencraft/network/RequestLinkPreviewPayload.java +++ b/src/main/java/net/dnorberg/rivencraft/network/RequestLinkPreviewPayload.java @@ -8,13 +8,14 @@ import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; -public record RequestLinkPreviewPayload(boolean fromStand, boolean offhand, BlockPos standPos) +public record RequestLinkPreviewPayload(boolean descriptiveBook, boolean fromStand, boolean offhand, BlockPos standPos) implements CustomPacketPayload { public static final Type TYPE = new Type<>( ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "request_link_preview") ); public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.BOOL, RequestLinkPreviewPayload::descriptiveBook, ByteBufCodecs.BOOL, RequestLinkPreviewPayload::fromStand, ByteBufCodecs.BOOL, RequestLinkPreviewPayload::offhand, BlockPos.STREAM_CODEC, RequestLinkPreviewPayload::standPos, diff --git a/src/main/resources/assets/rivencraft/lang/en_us.json b/src/main/resources/assets/rivencraft/lang/en_us.json index d044624..178db7e 100644 --- a/src/main/resources/assets/rivencraft/lang/en_us.json +++ b/src/main/resources/assets/rivencraft/lang/en_us.json @@ -22,7 +22,6 @@ "screen.rivencraft.book.bind": "Bind", "screen.rivencraft.book.link": "Link", "screen.rivencraft.book.unwritten": "This Descriptive Book is unwritten", - "screen.rivencraft.book.age_link": "Age Link", "screen.rivencraft.book.preview_pending": "Loading destination...", "screen.rivencraft.book.click_to_link": "Click the panel to link", "itemGroup.rivencraft": "Rivencraft"