Preview descriptive book destinations with Ponder
Build / build (push) Canceled after 0s

This commit is contained in:
dnorberg committed 2026-09-21 09:28:17 -04:00
1 parent 1be8194cfa
commit e30c0a25cc
9 files changed
+80 -39

No files matched your search

+4
View File
@@ -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.
+1 -1
View File
@@ -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
@@ -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);
}
});
}
@@ -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) {
@@ -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;
}
}
}
@@ -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
@@ -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;
}
@@ -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<RequestLinkPreviewPayload> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "request_link_preview")
);
public static final StreamCodec<RegistryFriendlyByteBuf, RequestLinkPreviewPayload> STREAM_CODEC =
StreamCodec.composite(
ByteBufCodecs.BOOL, RequestLinkPreviewPayload::descriptiveBook,
ByteBufCodecs.BOOL, RequestLinkPreviewPayload::fromStand,
ByteBufCodecs.BOOL, RequestLinkPreviewPayload::offhand,
BlockPos.STREAM_CODEC, RequestLinkPreviewPayload::standPos,
@@ -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"