mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-04-17 19:12:14 +02:00
Fix some edge-cases related to lecterns
This commit is contained in:
parent
966de4d62c
commit
ed45df2a2e
9 changed files with 68 additions and 65 deletions
core/src/main/java/org/geysermc/geyser
inventory
session
translator
inventory
protocol
util
|
@ -27,8 +27,6 @@ package org.geysermc.geyser.inventory;
|
|||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.java.inventory.JavaOpenBookTranslator;
|
||||
|
@ -40,8 +38,6 @@ public class LecternContainer extends Container {
|
|||
private int currentBedrockPage = 0;
|
||||
@Setter
|
||||
private NbtMap blockEntityTag;
|
||||
@Setter
|
||||
private Vector3i position;
|
||||
|
||||
private boolean isBookInPlayerInventory = false;
|
||||
|
||||
|
@ -49,24 +45,11 @@ public class LecternContainer extends Container {
|
|||
super(session, title, id, size, containerType);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the Java server asks the client to open a book in their hotbar, we create a fake lectern to show it to the client.
|
||||
* We can't use the {@link #isUsingRealBlock()} check as we may also be dealing with a real virtual lectern (with its own inventory).
|
||||
*/
|
||||
@Override
|
||||
public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) {
|
||||
if (isBookInPlayerInventory) {
|
||||
session.getPlayerInventory().setItem(slot, newItem, session);
|
||||
} else {
|
||||
super.setItem(slot, newItem, session);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used ONLY once to set the book of a fake lectern in {@link JavaOpenBookTranslator}.
|
||||
* See {@link LecternContainer#setItem(int, GeyserItemStack, GeyserSession)} as for why this is separate.
|
||||
*/
|
||||
public void setFakeLecternBook(GeyserItemStack book, GeyserSession session) {
|
||||
public void setVirtualLecternBook(GeyserItemStack book, GeyserSession session) {
|
||||
this.isBookInPlayerInventory = true;
|
||||
super.setItem(0, book, session);
|
||||
}
|
||||
|
|
|
@ -138,8 +138,9 @@ import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler;
|
|||
import org.geysermc.geyser.event.type.SessionDisconnectEventImpl;
|
||||
import org.geysermc.geyser.impl.camera.CameraDefinitions;
|
||||
import org.geysermc.geyser.impl.camera.GeyserCameraData;
|
||||
import org.geysermc.geyser.inventory.InventoryHolder;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.InventoryHolder;
|
||||
import org.geysermc.geyser.inventory.LecternContainer;
|
||||
import org.geysermc.geyser.inventory.PlayerInventory;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserSmithingRecipe;
|
||||
|
@ -299,7 +300,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
|
|||
private final InventoryHolder<PlayerInventory> playerInventoryHolder;
|
||||
|
||||
/**
|
||||
* Stores the current open inventory, including the correct translator.
|
||||
* Stores the current open Bedrock inventory, including the correct translator.
|
||||
* Prefer using {@link InventoryUtils#getInventory(GeyserSession, int)}, as this
|
||||
* method can e.g. return a {@code InventoryHolder<LecternContainer>} due to the
|
||||
* workaround in {@link LecternContainer#isBookInPlayerInventory()} workaround.
|
||||
*/
|
||||
@Setter
|
||||
private @Nullable InventoryHolder<? extends Inventory> inventoryHolder;
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.cloudburstmc.nbt.NbtMapBuilder;
|
|||
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
|
||||
import org.geysermc.erosion.util.LecternUtils;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.InventoryHolder;
|
||||
import org.geysermc.geyser.inventory.LecternContainer;
|
||||
import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater;
|
||||
import org.geysermc.geyser.level.block.Blocks;
|
||||
|
@ -55,7 +56,7 @@ public class LecternInventoryTranslator extends AbstractBlockInventoryTranslator
|
|||
private boolean receivedBook = false;
|
||||
|
||||
public LecternInventoryTranslator() {
|
||||
super(1, Blocks.LECTERN, org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.LECTERN , ContainerInventoryUpdater.INSTANCE);
|
||||
super(1, Blocks.LECTERN, org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.LECTERN, ContainerInventoryUpdater.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -87,7 +88,7 @@ public class LecternInventoryTranslator extends AbstractBlockInventoryTranslator
|
|||
// Of course, sending a simple ContainerClosePacket, or even breaking the block doesn't work to close a lectern.
|
||||
// Heck, the latter crashes the client xd
|
||||
// BDS just sends an empty base lectern tag... that kicks out the client. Fine. Let's do that!
|
||||
Vector3i position = container.isUsingRealBlock() ? session.getLastInteractionBlockPosition() : container.getHolderPosition();
|
||||
Vector3i position = container.getHolderPosition();
|
||||
var baseLecternTag = LecternUtils.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 0);
|
||||
BlockEntityUtils.updateBlockEntity(session, baseLecternTag.build(), position);
|
||||
|
||||
|
@ -109,7 +110,7 @@ public class LecternInventoryTranslator extends AbstractBlockInventoryTranslator
|
|||
if (key == 0) { // Lectern page update
|
||||
inventory.setCurrentBedrockPage(value / 2);
|
||||
inventory.setBlockEntityTag(inventory.getBlockEntityTag().toBuilder().putInt("page", inventory.getCurrentBedrockPage()).build());
|
||||
BlockEntityUtils.updateBlockEntity(session, inventory.getBlockEntityTag(), inventory.getPosition());
|
||||
BlockEntityUtils.updateBlockEntity(session, inventory.getBlockEntityTag(), inventory.getHolderPosition());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,11 +130,6 @@ public class LecternInventoryTranslator extends AbstractBlockInventoryTranslator
|
|||
|
||||
@Override
|
||||
public void updateSlot(GeyserSession session, LecternContainer container, int slot) {
|
||||
// If we're not in a real lectern, the Java server thinks we are still in the player inventory.
|
||||
if (container.isBookInPlayerInventory()) {
|
||||
session.getPlayerInventoryHolder().updateSlot(slot);
|
||||
return;
|
||||
}
|
||||
super.updateSlot(session, container, slot);
|
||||
if (slot == 0) {
|
||||
updateBook(session, container, container.getItem(0));
|
||||
|
@ -148,16 +144,19 @@ public class LecternInventoryTranslator extends AbstractBlockInventoryTranslator
|
|||
/**
|
||||
* Translate the data of the book in the lectern into a block entity tag.
|
||||
*/
|
||||
private void updateBook(GeyserSession session, LecternContainer inventory, GeyserItemStack book) {
|
||||
private void updateBook(GeyserSession session, LecternContainer container, GeyserItemStack book) {
|
||||
if (session.isDroppingLecternBook()) {
|
||||
// We have to enter the inventory GUI to eject the book
|
||||
ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getJavaId(), 3);
|
||||
session.sendDownstreamGamePacket(packet);
|
||||
session.setDroppingLecternBook(false);
|
||||
InventoryUtils.closeInventory(session, inventory.getJavaId(), false);
|
||||
} else if (inventory.getBlockEntityTag() == null) {
|
||||
Vector3i position = inventory.isUsingRealBlock() ?
|
||||
session.getLastInteractionBlockPosition() : inventory.getHolderPosition();
|
||||
InventoryHolder<?> holder = session.getInventoryHolder();
|
||||
if (holder != null && !container.isBookInPlayerInventory()) {
|
||||
// We have to enter the inventory GUI to eject the book
|
||||
ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(container.getJavaId(), 3);
|
||||
session.sendDownstreamGamePacket(packet);
|
||||
session.setDroppingLecternBook(false);
|
||||
InventoryUtils.sendJavaContainerClose(holder);
|
||||
InventoryUtils.closeInventory(session, container.getJavaId(), false);
|
||||
}
|
||||
} else if (container.getBlockEntityTag() == null) {
|
||||
Vector3i position = container.getHolderPosition();
|
||||
|
||||
NbtMap blockEntityTag;
|
||||
if (book.hasNonBaseComponents()) {
|
||||
|
@ -180,17 +179,14 @@ public class LecternInventoryTranslator extends AbstractBlockInventoryTranslator
|
|||
.putString("Name", "minecraft:written_book")
|
||||
.putCompound("tag", itemData.getTag())
|
||||
.build());
|
||||
lecternTag.putInt("page", inventory.getCurrentBedrockPage());
|
||||
lecternTag.putInt("page", container.getCurrentBedrockPage());
|
||||
blockEntityTag = lecternTag.build();
|
||||
} else {
|
||||
// There is *a* book here, but... no NBT.
|
||||
blockEntityTag = LecternBlock.getBaseLecternTag(position, true);
|
||||
}
|
||||
|
||||
// Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild
|
||||
// the block entity tag
|
||||
inventory.setBlockEntityTag(blockEntityTag);
|
||||
inventory.setPosition(position);
|
||||
container.setBlockEntityTag(blockEntityTag);
|
||||
|
||||
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position);
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ public class BedrockContainerCloseTranslator extends PacketTranslator<ContainerC
|
|||
if (holder != null) {
|
||||
if (bedrockId == holder.bedrockId()) {
|
||||
InventoryUtils.sendJavaContainerClose(holder);
|
||||
InventoryUtils.closeInventory(session, holder.javaId(), false);
|
||||
InventoryUtils.closeInventory(session, holder, false);
|
||||
} else if (holder.pending()) {
|
||||
InventoryUtils.displayInventory(holder);
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ public class BedrockLecternUpdateTranslator extends PacketTranslator<LecternUpda
|
|||
if (lecternContainer.getCurrentBedrockPage() == packet.getPage()) {
|
||||
// The same page means Bedrock is closing the window
|
||||
InventoryUtils.sendJavaContainerClose(holder);
|
||||
InventoryUtils.closeInventory(session, lecternContainer.getJavaId(), false);
|
||||
InventoryUtils.closeInventory(session, holder, false);
|
||||
} else {
|
||||
// Each "page" Bedrock gives to us actually represents two pages (think opening a book and seeing two pages)
|
||||
// Each "page" on Java is just one page (think a spiral notebook folded back to only show one page)
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
package org.geysermc.geyser.translator.protocol.java.inventory;
|
||||
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.InventoryHolder;
|
||||
import org.geysermc.geyser.inventory.LecternContainer;
|
||||
import org.geysermc.geyser.inventory.PlayerInventory;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
|
@ -40,8 +42,24 @@ public class JavaContainerCloseTranslator extends PacketTranslator<ClientboundCo
|
|||
public void translate(GeyserSession session, ClientboundContainerClosePacket packet) {
|
||||
// Sometimes the server can request a window close of ID 0... when the window isn't even open
|
||||
// Don't confirm in this instance
|
||||
Inventory inventory = session.getOpenInventory();
|
||||
session.setServerRequestedClosePlayerInventory(packet.getContainerId() == 0 && inventory instanceof PlayerInventory);
|
||||
InventoryUtils.closeInventory(session, packet.getContainerId(), (inventory != null && inventory.getJavaId() == packet.getContainerId()));
|
||||
InventoryHolder<? extends Inventory> holder = session.getInventoryHolder();
|
||||
boolean confirm = false;
|
||||
if (holder != null) {
|
||||
if (packet.getContainerId() == 0) {
|
||||
Inventory inventory = holder.inventory();
|
||||
if (inventory instanceof PlayerInventory) {
|
||||
session.setServerRequestedClosePlayerInventory(true);
|
||||
}
|
||||
|
||||
if (inventory instanceof LecternContainer container && container.isBookInPlayerInventory()) {
|
||||
InventoryUtils.closeInventory(session, holder, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
confirm = holder.javaId() == packet.getContainerId();
|
||||
}
|
||||
|
||||
InventoryUtils.closeInventory(session, packet.getContainerId(), confirm);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,13 +28,13 @@ package org.geysermc.geyser.translator.protocol.java.inventory;
|
|||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.InventoryHolder;
|
||||
import org.geysermc.geyser.inventory.LecternContainer;
|
||||
import org.geysermc.geyser.item.Items;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundOpenBookPacket;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@ -59,21 +59,22 @@ public class JavaOpenBookTranslator extends PacketTranslator<ClientboundOpenBook
|
|||
return;
|
||||
}
|
||||
|
||||
if (stack.asItem().equals(Items.WRITTEN_BOOK)) {
|
||||
// The item doesn't need to be a book; just needs to have either of these components.
|
||||
if (stack.hasNonBaseComponents() &&
|
||||
stack.getComponent(DataComponentTypes.WRITABLE_BOOK_CONTENT) != null ||
|
||||
stack.getComponent(DataComponentTypes.WRITTEN_BOOK_CONTENT) != null
|
||||
) {
|
||||
InventoryHolder<?> openInventory = session.getInventoryHolder();
|
||||
if (openInventory != null) {
|
||||
InventoryUtils.closeInventory(session, openInventory.javaId(), true);
|
||||
InventoryUtils.sendJavaContainerClose(openInventory);
|
||||
InventoryUtils.closeInventory(session, openInventory, true);
|
||||
}
|
||||
|
||||
//noinspection unchecked
|
||||
InventoryTranslator<LecternContainer> translator = (InventoryTranslator<LecternContainer>) InventoryTranslator.inventoryTranslator(ContainerType.LECTERN);
|
||||
Objects.requireNonNull(translator, "could not find lectern inventory translator!");
|
||||
|
||||
// Should never be null
|
||||
Objects.requireNonNull(translator, "lectern translator must exist");
|
||||
LecternContainer container = translator.createInventory(session, "", FAKE_LECTERN_WINDOW_ID, ContainerType.LECTERN);
|
||||
container.setFakeLecternBook(stack, session);
|
||||
container.setVirtualLecternBook(stack, session);
|
||||
InventoryUtils.openInventory(new InventoryHolder<>(session, container, translator));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ public class JavaOpenScreenTranslator extends PacketTranslator<ClientboundOpenSc
|
|||
// No translator exists for this window type. Close all windows and return.
|
||||
if (newTranslator == null) {
|
||||
if (currentInventory != null) {
|
||||
InventoryUtils.closeInventory(session, currentInventory.javaId(), true);
|
||||
InventoryUtils.closeInventory(session, currentInventory, true);
|
||||
}
|
||||
|
||||
ServerboundContainerClosePacket closeWindowPacket = new ServerboundContainerClosePacket(packet.getContainerId());
|
||||
|
@ -93,7 +93,7 @@ public class JavaOpenScreenTranslator extends PacketTranslator<ClientboundOpenSc
|
|||
return;
|
||||
}
|
||||
|
||||
InventoryUtils.closeInventory(session, currentInventory.javaId(), true);
|
||||
InventoryUtils.closeInventory(session, currentInventory, true);
|
||||
}
|
||||
|
||||
InventoryUtils.openInventory(newInventoryHolder);
|
||||
|
|
|
@ -39,7 +39,6 @@ import org.geysermc.geyser.GeyserImpl;
|
|||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.InventoryHolder;
|
||||
import org.geysermc.geyser.inventory.LecternContainer;
|
||||
import org.geysermc.geyser.inventory.click.Click;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
|
||||
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
|
||||
|
@ -63,6 +62,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.ItemSta
|
|||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.TagSlotDisplay;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.WithRemainderSlotDisplay;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundOpenBookPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClosePacket;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
|
@ -175,10 +175,14 @@ public class InventoryUtils {
|
|||
* @param confirm whether to wait for the session to process the close before opening a new inventory.
|
||||
*/
|
||||
public static void closeInventory(GeyserSession session, int javaId, boolean confirm) {
|
||||
InventoryHolder<?> holder = getInventory(session, javaId);
|
||||
closeInventory(session, holder, confirm);
|
||||
}
|
||||
|
||||
public static void closeInventory(GeyserSession session, InventoryHolder<?> holder, boolean confirm) {
|
||||
session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session);
|
||||
updateCursor(session);
|
||||
|
||||
InventoryHolder<?> holder = getInventory(session, javaId);
|
||||
if (holder != null) {
|
||||
holder.closeInventory();
|
||||
if (holder.shouldConfirmClose(confirm)) {
|
||||
|
@ -192,18 +196,15 @@ public class InventoryUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* A util method to get the an inventory based on a Java id. This is used over
|
||||
* {@link GeyserSession#getInventoryHolder()} when needing to account for player inventories instead of just the open inventory.
|
||||
* A util method to get an (open) inventory based on a Java id. This method should be used over
|
||||
* {@link GeyserSession#getOpenInventory()} (or {@link GeyserSession#getInventoryHolder()}) to account for an edge-case where the Java server expects
|
||||
* Geyser to have the player inventory open while we're using a virtual lectern for the {@link ClientboundOpenBookPacket}.
|
||||
*/
|
||||
public static @Nullable InventoryHolder<?> getInventory(GeyserSession session, int javaId) {
|
||||
InventoryHolder<?> holder = session.getInventoryHolder();
|
||||
if (javaId == 0) {
|
||||
// ugly hack: lecterns aren't their own inventory on Java, and can hence be closed with e.g. an id of 0
|
||||
if (holder != null && holder.inventory() instanceof LecternContainer) {
|
||||
return session.getInventoryHolder();
|
||||
}
|
||||
return session.getPlayerInventoryHolder();
|
||||
} else {
|
||||
InventoryHolder<?> holder = session.getInventoryHolder();
|
||||
if (holder != null && javaId == holder.javaId()) {
|
||||
return holder;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue