diff --git a/docs/PORTING.md b/docs/PORTING.md index a080f21..a60d92e 100644 --- a/docs/PORTING.md +++ b/docs/PORTING.md @@ -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 diff --git a/gradle.properties b/gradle.properties index 0aab4a9..6ac4715 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.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 diff --git a/src/main/java/net/dnorberg/rivencraft/block/BookStandBlock.java b/src/main/java/net/dnorberg/rivencraft/block/BookStandBlock.java index 65273ad..37d3174 100644 --- a/src/main/java/net/dnorberg/rivencraft/block/BookStandBlock.java +++ b/src/main/java/net/dnorberg/rivencraft/block/BookStandBlock.java @@ -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); } diff --git a/src/main/java/net/dnorberg/rivencraft/client/ClientBookInteractionHandler.java b/src/main/java/net/dnorberg/rivencraft/client/ClientBookInteractionHandler.java index f5851bd..56850c3 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/ClientBookInteractionHandler.java +++ b/src/main/java/net/dnorberg/rivencraft/client/ClientBookInteractionHandler.java @@ -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())); + } + } } diff --git a/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java b/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java index 8be65b9..8f28f7f 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java +++ b/src/main/java/net/dnorberg/rivencraft/client/ClientEvents.java @@ -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); } } 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 222a67c..fd0d99c 100644 --- a/src/main/java/net/dnorberg/rivencraft/client/screen/LinkingBookScreen.java +++ b/src/main/java/net/dnorberg/rivencraft/client/screen/LinkingBookScreen.java @@ -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(); } } diff --git a/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java b/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java index cb1c8f8..7b5d829 100644 --- a/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java +++ b/src/main/java/net/dnorberg/rivencraft/network/ModNetworking.java @@ -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; } diff --git a/src/main/java/net/dnorberg/rivencraft/network/UseBookStandPayload.java b/src/main/java/net/dnorberg/rivencraft/network/UseBookStandPayload.java new file mode 100644 index 0000000..be21e4f --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/network/UseBookStandPayload.java @@ -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 TYPE = new Type<>( + ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "use_book_stand") + ); + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + BlockPos.STREAM_CODEC, + UseBookStandPayload::pos, + UseBookStandPayload::new + ); + + @Override + public Type type() { + return TYPE; + } +}