diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java index 99dbc7763..a35dd5fc6 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -203,17 +203,27 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { return; } BookMeta bookMeta = (BookMeta) itemStack.getItemMeta(); - NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, bookMeta.getPageCount()); + // On the count: allow the book to show/open even there are no pages. We know there is a book here, after all, and this matches Java behavior + boolean hasBookPages = bookMeta.getPageCount() > 0; + NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, hasBookPages ? bookMeta.getPageCount() : 1); lecternTag.putInt("page", lectern.getPage() / 2); NbtMapBuilder bookTag = NbtMap.builder() .putByte("Count", (byte) itemStack.getAmount()) .putShort("Damage", (short) 0) .putString("Name", "minecraft:writable_book"); - List pages = new ArrayList<>(); - for (String page : bookMeta.getPages()) { + List pages = new ArrayList<>(bookMeta.getPageCount()); + if (hasBookPages) { + for (String page : bookMeta.getPages()) { + NbtMapBuilder pageBuilder = NbtMap.builder() + .putString("photoname", "") + .putString("text", page); + pages.add(pageBuilder.build()); + } + } else { + // Empty page NbtMapBuilder pageBuilder = NbtMap.builder() .putString("photoname", "") - .putString("text", page); + .putString("text", ""); pages.add(pageBuilder.build()); } bookTag.putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, pages).build()); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index f2fa98ba4..393d6275f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -175,7 +175,7 @@ public class GeyserSession implements CommandSender { * See {@link org.geysermc.connector.network.translators.world.WorldManager#getLecternDataAt(GeyserSession, int, int, int, boolean)} * for more information. */ - private final List lecternCache = new ArrayList<>(); + private final Set lecternCache = new ObjectOpenHashSet<>(); @Setter private boolean droppingLecternBook; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java index 05529da3e..084c4fc17 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java @@ -33,6 +33,7 @@ import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; @@ -43,6 +44,8 @@ import org.geysermc.connector.network.translators.inventory.updater.InventoryUpd import org.geysermc.connector.utils.BlockEntityUtils; import org.geysermc.connector.utils.InventoryUtils; +import java.util.Collections; + public class LecternInventoryTranslator extends BaseInventoryTranslator { private final InventoryUpdater updater; @@ -96,11 +99,12 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { // If the method returns true, this is already handled for us GeyserItemStack geyserItemStack = inventory.getItem(0); CompoundTag tag = geyserItemStack.getNbt(); + // Position has to be the last interacted position... right? + Vector3i position = session.getLastInteractionBlockPosition(); + // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet + boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); + NbtMap blockEntityTag; if (tag != null) { - // Position has to be the last interacted position... right? - Vector3i position = session.getLastInteractionBlockPosition(); - // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet - boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); int pagesSize = ((ListTag) tag.get("pages")).size(); ItemData itemData = geyserItemStack.getItemData(session); NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), pagesSize); @@ -111,20 +115,35 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { .putCompound("tag", itemData.getTag()) .build()); lecternTag.putInt("page", lecternContainer.getCurrentBedrockPage()); - NbtMap blockEntityTag = lecternTag.build(); - // Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild - // the block entity tag - lecternContainer.setBlockEntityTag(blockEntityTag); - lecternContainer.setPosition(position); - if (shouldRefresh) { - // Update the lectern because it's not updated client-side - BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position); - session.getLecternCache().add(position); - // Close the window - we will reopen it once the client has this data synced - ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId()); - session.sendDownstreamPacket(closeWindowPacket); - InventoryUtils.closeInventory(session, inventory.getId(), false); - } + blockEntityTag = lecternTag.build(); + } else { + // There is *a* book here, but... no NBT. + NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 1); + NbtMapBuilder bookTag = NbtMap.builder() + .putByte("Count", (byte) 1) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:writable_book") + .putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, Collections.singletonList( + NbtMap.builder() + .putString("photoname", "") + .putString("text", "") + .build() + )).build()); + + blockEntityTag = lecternTag.putCompound("book", bookTag.build()).build(); + } + // Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild + // the block entity tag + lecternContainer.setBlockEntityTag(blockEntityTag); + lecternContainer.setPosition(position); + if (shouldRefresh) { + // Update the lectern because it's not updated client-side + BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position); + session.getLecternCache().add(position); + // Close the window - we will reopen it once the client has this data synced + ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId()); + session.sendDownstreamPacket(closeWindowPacket); + InventoryUtils.closeInventory(session, inventory.getId(), false); } } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index 1b13d5dea..45785545c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -401,7 +401,6 @@ public class ChunkUtils { boolean newLecternHasBook = BlockStateValues.getLecternBookStates().get(blockState); if (!session.getConnector().getWorldManager().shouldExpectLecternHandled() && lecternCachedHasBook != newLecternHasBook) { // Refresh the block entirely - it either has a book or no longer has a book - session.getConnector().getLogger().warning("Refreshing lectern entirely"); NbtMap newLecternTag; if (newLecternHasBook) { newLecternTag = session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false);