Open stored books from book stands
Build / build (push) Canceled after 0s

This commit is contained in:
dnorberg committed 2026-09-21 07:59:36 -04:00
1 parent 7b3bc328f2
commit f57afab363
8 files changed
+89 -9

No files matched your search

+5 -1
View File
@@ -53,7 +53,7 @@ the symbol grammar is introduced.
- [x] Ponder dependency and isolated destination-preview level boundary.
- [ ] Server-provided destination snapshots rendered as rotating Ponder scenes.
- [x] Book Stand floating names synchronized from the stored book stack.
- [ ] Open the stored book screen from a Book Stand.
- [x] Open the stored book screen from a Book Stand with server-validated linking.
- [ ] Automated in-game tests for binding, persistence, and travel.
The temporary shapeless Linking Book recipe makes the linking subsystem easy
@@ -103,6 +103,10 @@ its display name as a camera-facing label above the book.
Version 0.1.10 corrects the nameplate transform and matches Minecraft's normal
plus see-through text passes so angled stand geometry cannot hide the label.
Version 0.1.11 opens the stored book interface when a Book Stand is used with
an empty hand. Linking Books travel through the preview panel, and the server
revalidates the stand position, interaction distance, stored item, and target.
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
+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.1.10
mod_version=0.1.11
# 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
@@ -2,9 +2,7 @@ package net.dnorberg.rivencraft.block;
import com.mojang.serialization.MapCodec;
import net.dnorberg.rivencraft.block.entity.BookStandBlockEntity;
import net.dnorberg.rivencraft.item.LinkingBookItem;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionResult;
@@ -63,10 +61,6 @@ public final class BookStandBlock extends LecternBlock {
return InteractionResult.sidedSuccess(level.isClientSide);
}
ItemStack book = stand.getBook();
if (book.getItem() instanceof LinkingBookItem && player instanceof ServerPlayer serverPlayer) {
LinkingBookItem.useBoundBook(serverPlayer, book);
}
return InteractionResult.sidedSuccess(level.isClientSide);
}
@@ -2,9 +2,12 @@ package net.dnorberg.rivencraft.client;
import net.dnorberg.rivencraft.client.screen.LinkingBookScreen;
import net.dnorberg.rivencraft.client.screen.DescriptiveBookScreen;
import net.dnorberg.rivencraft.block.entity.BookStandBlockEntity;
import net.dnorberg.rivencraft.registry.ModBlocks;
import net.dnorberg.rivencraft.registry.ModItems;
import net.minecraft.client.Minecraft;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.item.ItemStack;
import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent;
public final class ClientBookInteractionHandler {
@@ -32,4 +35,27 @@ public final class ClientBookInteractionHandler {
Minecraft.getInstance().setScreen(new DescriptiveBookScreen(event.getItemStack().copy()));
}
}
public static void onRightClickBlock(PlayerInteractEvent.RightClickBlock event) {
if (!event.getEntity().level().isClientSide()
|| event.getEntity().isShiftKeyDown()
|| !event.getItemStack().isEmpty()
|| !event.getLevel().getBlockState(event.getPos()).is(ModBlocks.BOOK_STAND.get())
|| !(event.getLevel().getBlockEntity(event.getPos()) instanceof BookStandBlockEntity stand)) {
return;
}
ItemStack book = stand.getBook();
if (book.isEmpty()) {
return;
}
event.setCanceled(true);
event.setCancellationResult(InteractionResult.SUCCESS);
if (book.is(ModItems.LINKING_BOOK.get())) {
Minecraft.getInstance().setScreen(new LinkingBookScreen(event.getPos(), book.copy()));
} else if (book.is(ModItems.DESCRIPTIVE_BOOK.get())) {
Minecraft.getInstance().setScreen(new DescriptiveBookScreen(book.copy()));
}
}
}
@@ -22,5 +22,6 @@ public final class ClientEvents {
@SubscribeEvent
public static void clientSetup(FMLClientSetupEvent event) {
NeoForge.EVENT_BUS.addListener(ClientBookInteractionHandler::onRightClickItem);
NeoForge.EVENT_BUS.addListener(ClientBookInteractionHandler::onRightClickBlock);
}
}
@@ -6,6 +6,7 @@ import net.dnorberg.rivencraft.client.preview.PonderDestinationPreviewRenderer;
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.registry.ModDataComponents;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Button;
@@ -35,6 +36,7 @@ public final class LinkingBookScreen extends Screen {
private final InteractionHand hand;
private final ItemStack book;
private final LinkTarget target;
private final BlockPos bookStandPos;
private DestinationPreviewRenderer preview;
private EditBox titleInput;
@@ -43,6 +45,15 @@ public final class LinkingBookScreen extends Screen {
this.hand = hand;
this.book = book;
target = book.get(ModDataComponents.LINK_TARGET.get());
bookStandPos = null;
}
public LinkingBookScreen(BlockPos bookStandPos, ItemStack book) {
super(book.getHoverName());
hand = InteractionHand.MAIN_HAND;
this.book = book;
target = book.get(ModDataComponents.LINK_TARGET.get());
this.bookStandPos = bookStandPos.immutable();
}
@Override
@@ -140,7 +151,11 @@ public final class LinkingBookScreen extends Screen {
}
private void link() {
PacketDistributor.sendToServer(new UseLinkingBookPayload(hand == InteractionHand.OFF_HAND));
if (bookStandPos == null) {
PacketDistributor.sendToServer(new UseLinkingBookPayload(hand == InteractionHand.OFF_HAND));
} else {
PacketDistributor.sendToServer(new UseBookStandPayload(bookStandPos));
}
onClose();
}
}
@@ -1,6 +1,7 @@
package net.dnorberg.rivencraft.network;
import net.dnorberg.rivencraft.book.LinkTarget;
import net.dnorberg.rivencraft.block.entity.BookStandBlockEntity;
import net.dnorberg.rivencraft.item.LinkingBookItem;
import net.dnorberg.rivencraft.registry.ModDataComponents;
import net.dnorberg.rivencraft.registry.ModItems;
@@ -11,6 +12,7 @@ import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.Vec3;
import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.network.registration.PayloadRegistrar;
@@ -25,6 +27,7 @@ public final class ModNetworking {
PayloadRegistrar registrar = event.registrar("1");
registrar.playToServer(BindLinkingBookPayload.TYPE, BindLinkingBookPayload.STREAM_CODEC, ModNetworking::bindBook);
registrar.playToServer(UseLinkingBookPayload.TYPE, UseLinkingBookPayload.STREAM_CODEC, ModNetworking::useBook);
registrar.playToServer(UseBookStandPayload.TYPE, UseBookStandPayload.STREAM_CODEC, ModNetworking::useBookStand);
}
private static void bindBook(BindLinkingBookPayload payload, IPayloadContext context) {
@@ -59,6 +62,19 @@ public final class ModNetworking {
}
}
private static void useBookStand(UseBookStandPayload payload, IPayloadContext context) {
if (!(context.player() instanceof ServerPlayer player)
|| player.distanceToSqr(Vec3.atCenterOf(payload.pos())) > 64.0D
|| !(player.level().getBlockEntity(payload.pos()) instanceof BookStandBlockEntity stand)) {
return;
}
ItemStack stack = stand.getBook();
if (stack.is(ModItems.LINKING_BOOK.get()) && stack.has(ModDataComponents.LINK_TARGET.get())) {
LinkingBookItem.useBoundBook(player, stack);
}
}
private static InteractionHand hand(boolean offhand) {
return offhand ? InteractionHand.OFF_HAND : InteractionHand.MAIN_HAND;
}
@@ -0,0 +1,24 @@
package net.dnorberg.rivencraft.network;
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;
public record UseBookStandPayload(BlockPos pos) implements CustomPacketPayload {
public static final Type<UseBookStandPayload> TYPE = new Type<>(
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "use_book_stand")
);
public static final StreamCodec<RegistryFriendlyByteBuf, UseBookStandPayload> STREAM_CODEC = StreamCodec.composite(
BlockPos.STREAM_CODEC,
UseBookStandPayload::pos,
UseBookStandPayload::new
);
@Override
public Type<? extends CustomPacketPayload> type() {
return TYPE;
}
}