1
0
Fork 0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-04-17 19:12:14 +02:00
This commit is contained in:
chris 2025-04-13 18:53:40 +08:00 committed by GitHub
commit 845e9bf134
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
81 changed files with 1023 additions and 847 deletions
core/src/main/java/org/geysermc/geyser
command/defaults
inventory
session
translator
inventory
protocol
util

View file

@ -51,6 +51,6 @@ public class AdvancedTooltipsCommand extends GeyserCommand {
+ MinecraftLocale.getLocaleString("debug.prefix", session.locale())
+ " " + ChatColor.RESET
+ MinecraftLocale.getLocaleString("debug.advanced_tooltips." + onOrOff, session.locale()));
session.getPlayerInventory().updateInventory();
session.getPlayerInventoryHolder().updateInventory();
}
}

View file

@ -30,7 +30,6 @@ import lombok.Setter;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
@ -63,8 +62,8 @@ public class AnvilContainer extends Container {
private int lastTargetSlot = -1;
public AnvilContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
super(session, title, id, size, containerType, playerInventory, translator);
public AnvilContainer(GeyserSession session, String title, int id, int size, ContainerType containerType) {
super(session, title, id, size, containerType);
}
/**

View file

@ -25,11 +25,10 @@
package org.geysermc.geyser.inventory;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
@Getter
@Setter
@ -37,7 +36,7 @@ public class BeaconContainer extends Container {
private int primaryId;
private int secondaryId;
public BeaconContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
super(session, title, id, size, containerType, playerInventory, translator);
public BeaconContainer(GeyserSession session, String title, int id, int size, ContainerType containerType) {
super(session, title, id, size, containerType);
}
}

View file

@ -26,11 +26,10 @@
package org.geysermc.geyser.inventory;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
public class CartographyContainer extends Container {
public CartographyContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
super(session, title, id, size, containerType, playerInventory, translator);
public CartographyContainer(GeyserSession session, String title, int id, int size, ContainerType containerType) {
super(session, title, id, size, containerType);
}
}

View file

@ -46,9 +46,9 @@ public class Container extends Inventory {
*/
private boolean isUsingRealBlock = false;
public Container(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
super(session, title, id, size, containerType, translator);
this.playerInventory = playerInventory;
public Container(GeyserSession session, String title, int id, int size, ContainerType containerType) {
super(session, title, id, size, containerType);
this.playerInventory = session.getPlayerInventory();
this.containerSize = this.size + InventoryTranslator.PLAYER_INVENTORY_SIZE;
}

View file

@ -48,8 +48,8 @@ public class CrafterContainer extends Container {
*/
private short disabledSlotsMask = 0;
public CrafterContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
super(session, title, id, size, containerType, playerInventory, translator);
public CrafterContainer(GeyserSession session, String title, int id, int size, ContainerType containerType) {
super(session, title, id, size, containerType);
}
@Override

View file

@ -26,7 +26,6 @@
package org.geysermc.geyser.inventory;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.data.inventory.EnchantOptionData;
import lombok.Getter;
@ -42,8 +41,8 @@ public class EnchantingContainer extends Container {
*/
private final GeyserEnchantOption[] geyserEnchantOptions;
public EnchantingContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
super(session, title, id, size, containerType, playerInventory, translator);
public EnchantingContainer(GeyserSession session, String title, int id, int size, ContainerType containerType) {
super(session, title, id, size, containerType);
enchantOptions = new EnchantOptionData[3];
geyserEnchantOptions = new GeyserEnchantOption[3];

View file

@ -30,7 +30,6 @@ import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.Generic3X3InventoryTranslator;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
@Getter
@ -38,12 +37,12 @@ public class Generic3X3Container extends Container {
/**
* Whether we need to set the container type as {@link org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType#DROPPER}.
* <p>
* Used at {@link Generic3X3InventoryTranslator#openInventory(GeyserSession, Inventory)}
* Used at {@link Generic3X3InventoryTranslator#openInventory(GeyserSession, Generic3X3Container)}
*/
private boolean isDropper = false;
public Generic3X3Container(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
super(session, title, id, size, containerType, playerInventory, translator);
public Generic3X3Container(GeyserSession session, String title, int id, int size, ContainerType containerType) {
super(session, title, id, size, containerType);
}
@Override

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2025 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.inventory;
import lombok.Getter;
import lombok.Setter;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
/**
* A "hack" to be able to use existing barrels.
* The only difference to chests appears to be the different ContainerSlotType - this accounts for it.
*/
@Getter @Setter
public class Generic9X3Container extends Container {
private boolean isBarrel;
public Generic9X3Container(GeyserSession session, String title, int id, int size, ContainerType containerType) {
super(session, title, id, size, containerType);
}
@Override
public void setUsingRealBlock(boolean usingRealBlock, Block block) {
super.setUsingRealBlock(usingRealBlock, block);
if (usingRealBlock) {
isBarrel = block == Blocks.BARREL;
}
}
}

View file

@ -35,7 +35,6 @@ import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.click.ClickPlan;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
@ -95,44 +94,24 @@ public abstract class Inventory {
@Setter
protected long holderId = -1;
/**
* Whether this inventory is currently pending.
* It can be pending if this inventory was opened while another inventory was still open,
* or because opening this inventory takes more time (e.g. virtual inventories).
*/
@Getter
@Setter
private boolean pending = false;
/**
* Whether this inventory is currently shown to the Bedrock player.
*/
@Getter
@Setter
private boolean displayed = false;
private boolean displayed;
/**
* The translator for this inventory. Stored here to avoid de-syncs of the inventory and current translator.
*/
@Getter
private final InventoryTranslator translator;
@Getter
private final GeyserSession session;
protected Inventory(GeyserSession session, int id, int size, ContainerType containerType, InventoryTranslator translator) {
this(session, "Inventory", id, size, containerType, translator);
protected Inventory(GeyserSession session, int id, int size, ContainerType containerType) {
this(session, "Inventory", id, size, containerType);
}
protected Inventory(GeyserSession session, String title, int javaId, int size, ContainerType containerType, InventoryTranslator translator) {
protected Inventory(GeyserSession session, String title, int javaId, int size, ContainerType containerType) {
this.title = title;
this.javaId = javaId;
this.size = size;
this.containerType = containerType;
this.items = new GeyserItemStack[size];
Arrays.fill(items, GeyserItemStack.EMPTY);
this.translator = translator;
this.session = session;
// This is to prevent conflicts with special bedrock inventory IDs.
// The vanilla java server only sends an ID between 1 and 100 when opening an inventory,
@ -144,7 +123,7 @@ public abstract class Inventory {
// Java wouldn't - e.g. for virtual chest menus that switch pages.
// And, well, we want to avoid reusing Bedrock inventory id's that are currently being used in a closing inventory;
// so to be safe we just deviate in that case as well.
if ((session.getOpenInventory() != null && session.getOpenInventory().getBedrockId() == bedrockId) || session.isClosingInventory()) {
if ((session.getInventoryHolder() != null && session.getInventoryHolder().bedrockId() == bedrockId) || session.isClosingInventory()) {
this.bedrockId += 1;
}
}
@ -214,20 +193,4 @@ public abstract class Inventory {
public boolean shouldConfirmContainerClose() {
return true;
}
/*
* Helper methods to avoid using the wrong translator to update specific inventories.
*/
public void updateInventory() {
this.translator.updateInventory(session, this);
}
public void updateProperty(int rawProperty, int value) {
this.translator.updateProperty(session, this, rawProperty, value);
}
public void updateSlot(int slot) {
this.translator.updateSlot(session, this, slot);
}
}

View file

@ -0,0 +1,165 @@
/*
* Copyright (c) 2025 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/
package org.geysermc.geyser.inventory;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequest;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import java.util.List;
/**
* A helper class storing the current inventory, translator, and session.
*/
@Accessors(fluent = true)
@Getter
public final class InventoryHolder<T extends Inventory> {
private final GeyserSession session;
private final T inventory;
private final InventoryTranslator<T> translator;
/**
* Whether this inventory is currently pending.
* It can be pending if this inventory was opened while another inventory was still open,
* or because opening this inventory takes more time (e.g. virtual inventories).
*/
@Setter
private boolean pending;
/**
* Stores the number of attempts to open virtual inventories.
* Capped at 3, and isn't used in ideal circumstances.
* Used to resolve <a href="https://github.com/GeyserMC/Geyser/issues/5426">container closing issues.</a>
*/
@Setter
private int containerOpenAttempts;
@SuppressWarnings("unchecked")
public InventoryHolder(GeyserSession session, Inventory newInventory, InventoryTranslator<? extends Inventory> newTranslator) {
this.session = session;
this.inventory = (T) newInventory;
this.translator = (InventoryTranslator<T>) newTranslator;
}
public void markCurrent() {
this.session.setInventoryHolder(this);
}
public boolean shouldSetPending() {
return session.isClosingInventory() || !session.getUpstream().isInitialized() || session.getPendingOrCurrentBedrockInventoryId() != -1;
}
public boolean shouldConfirmClose(boolean confirm) {
return confirm && inventory.isDisplayed() && !pending && !(inventory instanceof LecternContainer);
}
public void inheritFromExisting(InventoryHolder<? extends Inventory> existing) {
inventory.setBedrockId(existing.inventory.getBedrockId());
// Also mirror other properties - in case we're e.g. dealing with a pending virtual inventory
this.pending = existing.pending();
inventory.setDisplayed(existing.inventory().isDisplayed());
inventory.setHolderPosition(existing.inventory().getHolderPosition());
inventory.setHolderId(existing.inventory().getHolderId());
this.markCurrent();
}
/*
* Helper methods to avoid using the wrong translator to update specific inventories.
*/
public void updateInventory() {
this.translator.updateInventory(session, inventory);
}
public void updateProperty(int rawProperty, int value) {
this.translator.updateProperty(session, inventory, rawProperty, value);
}
public void updateSlot(int slot) {
this.translator.updateSlot(session, inventory, slot);
}
public void openInventory() {
this.translator.openInventory(session, inventory);
this.pending = false;
this.inventory.setDisplayed(true);
}
public void closeInventory(boolean force) {
this.translator.closeInventory(session, inventory, force);
if (session.getContainerOutputFuture() != null) {
session.getContainerOutputFuture().cancel(true);
}
}
public boolean requiresOpeningDelay() {
return this.translator.requiresOpeningDelay(session, inventory);
}
public boolean prepareInventory() {
return this.translator.prepareInventory(session, inventory);
}
public void translateRequests(List<ItemStackRequest> requests) {
this.translator.translateRequests(session, inventory, requests);
}
public GeyserSession session() {
return session;
}
public T inventory() {
return inventory;
}
public InventoryTranslator<T> translator() {
return translator;
}
public void incrementContainerOpenAttempts() {
this.containerOpenAttempts++;
}
public int javaId() {
return inventory.getJavaId();
}
public int bedrockId() {
return inventory.getBedrockId();
}
@Override
public String toString() {
return "InventoryHolder[" +
"session=" + session + ", " +
"inventory=" + inventory + ", " +
"translator=" + translator + ']';
}
}

View file

@ -27,11 +27,8 @@ 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.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.protocol.java.inventory.JavaOpenBookTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
@ -41,33 +38,18 @@ public class LecternContainer extends Container {
private int currentBedrockPage = 0;
@Setter
private NbtMap blockEntityTag;
@Setter
private Vector3i position;
private boolean isBookInPlayerInventory = false;
public LecternContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
super(session, title, id, size, containerType, playerInventory, translator);
}
/**
* 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);
}
public LecternContainer(GeyserSession session, String title, int id, int size, ContainerType containerType) {
super(session, title, id, size, containerType);
}
/**
* 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);
}

View file

@ -30,7 +30,6 @@ import lombok.Setter;
import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes;
import org.geysermc.geyser.entity.type.Entity;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.VillagerTrade;
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundMerchantOffersPacket;
@ -47,8 +46,8 @@ public class MerchantContainer extends Container {
@Getter
private int tradeExperience;
public MerchantContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
super(session, title, id, size, containerType, playerInventory, translator);
public MerchantContainer(GeyserSession session, String title, int id, int size, ContainerType containerType) {
super(session, title, id, size, containerType);
}
public void onTradeSelected(GeyserSession session, int slot) {

View file

@ -31,7 +31,6 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.item.type.Item;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.Hand;
import org.jetbrains.annotations.Range;
@ -48,7 +47,7 @@ public class PlayerInventory extends Inventory {
private GeyserItemStack cursor = GeyserItemStack.EMPTY;
public PlayerInventory(GeyserSession session) {
super(session, 0, 46, null, InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
super(session, 0, 46, null);
heldItemSlot = 0;
}

View file

@ -29,7 +29,6 @@ import lombok.Getter;
import lombok.Setter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
@Setter
@ -40,8 +39,8 @@ public class StonecutterContainer extends Container {
*/
private int stonecutterButton = -1;
public StonecutterContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
super(session, title, id, size, containerType, playerInventory, translator);
public StonecutterContainer(GeyserSession session, String title, int id, int size, ContainerType containerType) {
super(session, title, id, size, containerType);
}
@Override

View file

@ -66,11 +66,11 @@ public final class ClickPlan {
private boolean executionBegan;
private final GeyserSession session;
private final InventoryTranslator translator;
private final InventoryTranslator<?> translator;
private final Inventory inventory;
private final int gridSize;
public ClickPlan(GeyserSession session, InventoryTranslator translator, Inventory inventory) {
public ClickPlan(GeyserSession session, InventoryTranslator<?> translator, Inventory inventory) {
this.session = session;
this.translator = translator;
this.inventory = inventory;

View file

@ -90,15 +90,15 @@ public class BlockInventoryHolder extends InventoryHolder {
if (Objects.equals(position, previous.getHolderPosition())) {
return true;
} else {
GeyserImpl.getInstance().getLogger().debug(session, "Not reusing inventory (%s) due to virtual block holder changing (%s -> %s)!",
InventoryUtils.debugInventory(container), previous.getHolderPosition(), position);
GeyserImpl.getInstance().getLogger().debug(session, "Not reusing inventory due to virtual block holder changing (%s -> %s)!",
previous.getHolderPosition(), position);
return false;
}
}
@Override
public boolean prepareInventory(GeyserSession session, Container inventory) {
if (canUseRealBlock(session, inventory)) {
public boolean prepareInventory(GeyserSession session, Container container) {
if (canUseRealBlock(session, container)) {
return true;
}
@ -113,14 +113,14 @@ public class BlockInventoryHolder extends InventoryHolder {
blockPacket.setDefinition(session.getBlockMappings().getVanillaBedrockBlock(defaultJavaBlockState));
blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
session.sendUpstreamPacket(blockPacket);
inventory.setHolderPosition(position);
container.setHolderPosition(position);
setCustomName(session, position, inventory, defaultJavaBlockState);
setCustomName(session, position, container, defaultJavaBlockState);
return true;
}
protected boolean canUseRealBlock(GeyserSession session, Container inventory) {
protected boolean canUseRealBlock(GeyserSession session, Container container) {
// Check to see if there is an existing block we can use that the player just selected.
// First, verify that the player's position has not changed, so we don't try to select a block wildly out of range.
// (This could be a virtual inventory that the player is opening)
@ -131,9 +131,9 @@ public class BlockInventoryHolder extends InventoryHolder {
if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(state.javaId())) {
if (isValidBlock(state)) {
// We can safely use this block
inventory.setHolderPosition(session.getLastInteractionBlockPosition());
inventory.setUsingRealBlock(true, state.block());
setCustomName(session, session.getLastInteractionBlockPosition(), inventory, state);
container.setHolderPosition(session.getLastInteractionBlockPosition());
container.setUsingRealBlock(true, state.block());
setCustomName(session, session.getLastInteractionBlockPosition(), container, state);
return true;
}

View file

@ -30,8 +30,8 @@ import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.session.GeyserSession;
public abstract class InventoryHolder {
public abstract boolean canReuseContainer(GeyserSession session, Container inventory, Container oldInventory);
public abstract boolean prepareInventory(GeyserSession session, Container inventory);
public abstract void openInventory(GeyserSession session, Container inventory);
public abstract void closeInventory(GeyserSession session, Container inventory, ContainerType containerType);
public abstract boolean canReuseContainer(GeyserSession session, Container container, Container oldInventory);
public abstract boolean prepareInventory(GeyserSession session, Container container);
public abstract void openInventory(GeyserSession session, Container container);
public abstract void closeInventory(GeyserSession session, Container container, ContainerType containerType);
}

View file

@ -60,7 +60,7 @@ public class AnvilInventoryUpdater extends InventoryUpdater {
private static final int MAX_LEVEL_COST = 40;
@Override
public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
public void updateInventory(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory) {
super.updateInventory(translator, session, inventory);
AnvilContainer anvilContainer = (AnvilContainer) inventory;
updateInventoryState(session, anvilContainer);
@ -82,7 +82,7 @@ public class AnvilInventoryUpdater extends InventoryUpdater {
}
@Override
public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) {
public boolean updateSlot(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory, int javaSlot) {
if (super.updateSlot(translator, session, inventory, javaSlot))
return true;
AnvilContainer anvilContainer = (AnvilContainer) inventory;
@ -151,7 +151,7 @@ public class AnvilInventoryUpdater extends InventoryUpdater {
return 0;
}
private void updateTargetSlot(InventoryTranslator translator, GeyserSession session, AnvilContainer anvilContainer, int slot) {
private void updateTargetSlot(InventoryTranslator<?> translator, GeyserSession session, AnvilContainer anvilContainer, int slot) {
ItemData itemData = anvilContainer.getItem(slot).getItemData(session);
itemData = hijackRepairCost(session, anvilContainer, itemData);

View file

@ -46,7 +46,7 @@ public class ChestInventoryUpdater extends InventoryUpdater {
private final int paddedSize;
@Override
public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
public void updateInventory(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory) {
super.updateInventory(translator, session, inventory);
List<ItemData> bedrockItems = new ArrayList<>(paddedSize);
@ -65,7 +65,7 @@ public class ChestInventoryUpdater extends InventoryUpdater {
}
@Override
public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) {
public boolean updateSlot(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory, int javaSlot) {
if (super.updateSlot(translator, session, inventory, javaSlot))
return true;

View file

@ -38,7 +38,7 @@ public class ContainerInventoryUpdater extends InventoryUpdater {
public static final ContainerInventoryUpdater INSTANCE = new ContainerInventoryUpdater();
@Override
public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
public void updateInventory(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory) {
super.updateInventory(translator, session, inventory);
ItemData[] bedrockItems = new ItemData[translator.size];
@ -53,7 +53,7 @@ public class ContainerInventoryUpdater extends InventoryUpdater {
}
@Override
public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) {
public boolean updateSlot(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory, int javaSlot) {
if (super.updateSlot(translator, session, inventory, javaSlot))
return true;

View file

@ -44,7 +44,7 @@ public class CrafterInventoryUpdater extends InventoryUpdater {
public static final CrafterInventoryUpdater INSTANCE = new CrafterInventoryUpdater();
@Override
public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
public void updateInventory(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory) {
ItemData[] bedrockItems;
InventoryContentPacket contentPacket;
@ -74,7 +74,7 @@ public class CrafterInventoryUpdater extends InventoryUpdater {
}
@Override
public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) {
public boolean updateSlot(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory, int javaSlot) {
int containerId;
if (javaSlot < CrafterInventoryTranslator.GRID_SIZE || javaSlot == CrafterInventoryTranslator.JAVA_RESULT_SLOT) {
// Parts of the Crafter UI

View file

@ -38,7 +38,7 @@ public class HorseInventoryUpdater extends InventoryUpdater {
public static final HorseInventoryUpdater INSTANCE = new HorseInventoryUpdater();
@Override
public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
public void updateInventory(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory) {
super.updateInventory(translator, session, inventory);
ItemData[] bedrockItems = new ItemData[translator.size];
@ -53,7 +53,7 @@ public class HorseInventoryUpdater extends InventoryUpdater {
}
@Override
public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) {
public boolean updateSlot(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory, int javaSlot) {
if (super.updateSlot(translator, session, inventory, javaSlot))
return true;

View file

@ -36,7 +36,7 @@ import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import java.util.Arrays;
public class InventoryUpdater {
public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
public void updateInventory(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory) {
ItemData[] bedrockItems = new ItemData[36];
for (int i = 0; i < 36; i++) {
final int offset = i < 9 ? 27 : -9;
@ -48,7 +48,7 @@ public class InventoryUpdater {
session.sendUpstreamPacket(contentPacket);
}
public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) {
public boolean updateSlot(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory, int javaSlot) {
if (javaSlot >= translator.size) {
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(ContainerId.INVENTORY);

View file

@ -35,7 +35,7 @@ public class UIInventoryUpdater extends InventoryUpdater {
public static final UIInventoryUpdater INSTANCE = new UIInventoryUpdater();
@Override
public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
public void updateInventory(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory) {
super.updateInventory(translator, session, inventory);
for (int i = 0; i < translator.size; i++) {
@ -51,7 +51,7 @@ public class UIInventoryUpdater extends InventoryUpdater {
}
@Override
public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) {
public boolean updateSlot(InventoryTranslator<?> translator, GeyserSession session, Inventory inventory, int javaSlot) {
if (super.updateSlot(translator, session, inventory, javaSlot))
return true;

View file

@ -139,6 +139,8 @@ 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.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;
@ -175,6 +177,8 @@ import org.geysermc.geyser.session.cache.WorldBorder;
import org.geysermc.geyser.session.cache.WorldCache;
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator;
import org.geysermc.geyser.translator.text.MessageTranslator;
import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.EntityUtils;
@ -290,11 +294,24 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
*/
private boolean isInWorldBorderWarningArea = false;
private final PlayerInventory playerInventory;
/**
* Stores the player inventory and player inventory translator
*/
private final InventoryHolder<PlayerInventory> playerInventoryHolder;
/**
* 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 Inventory openInventory;
private @Nullable InventoryHolder<? extends Inventory> inventoryHolder;
/**
* Whether the client is currently closing an inventory.
* Used to open new inventories while another one is currently open.
*/
@Setter
private boolean closingInventory;
@ -532,14 +549,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Setter
private boolean placedBucket;
/**
* Stores whether the Java server requested the player inventory to be closed.
* Used to prevent our hacky player inventory closing workaround in {@link org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator#closeInventory(GeyserSession, Inventory)}
* to run when the closing is initated by the Bedrock client.
*/
@Setter
private boolean serverRequestedClosePlayerInventory;
/**
* Counts how many ticks have occurred since an arm animation started.
* -1 means there is no active arm swing
@ -681,15 +690,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
@Setter
private int stepTicks = 0;
/*
* Stores the number of attempts to open virtual inventories.
* Capped at 3, and isn't used in ideal circumstances.
* Used to resolve https://github.com/GeyserMC/Geyser/issues/5426
*/
@Setter
private int containerOpenAttempts;
public GeyserSession(GeyserImpl geyser, BedrockServerSession bedrockServerSession, EventLoop tickEventLoop) {
this.geyser = geyser;
this.upstream = new UpstreamSession(bedrockServerSession);
@ -723,8 +723,8 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
this.playerEntity = new SessionPlayerEntity(this);
collisionManager.updatePlayerBoundingBox(this.playerEntity.getPosition());
this.playerInventory = new PlayerInventory(this);
this.openInventory = null;
this.playerInventoryHolder = new InventoryHolder<>(this, new PlayerInventory(this), InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
this.inventoryHolder = null;
this.craftingRecipes = new Int2ObjectOpenHashMap<>();
this.javaToBedrockRecipeIds = new Int2ObjectOpenHashMap<>();
this.lastRecipeNetId = new AtomicInteger(InventoryUtils.LAST_RECIPE_NET_ID + 1);
@ -1239,10 +1239,6 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
worldTicks++;
}
public void setAuthenticationData(AuthData authData) {
this.authData = authData;
}
public void startSneaking() {
// Toggle the shield, if there is no ongoing arm animation
// This matches Bedrock Edition behavior as of 1.18.12
@ -1332,8 +1328,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
public void setClientData(BedrockClientData data) {
this.clientData = data;
this.inputCache.setInputMode(
org.cloudburstmc.protocol.bedrock.data.InputMode.values()[data.getCurrentInputMode().ordinal()]);
this.inputCache.setInputMode(org.cloudburstmc.protocol.bedrock.data.InputMode.values()[data.getCurrentInputMode().ordinal()]);
}
/**
@ -1356,9 +1351,9 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
* blocking and sends a packet to the Java server.
*/
private boolean attemptToBlock() {
if (playerInventory.getItemInHand().asItem() == Items.SHIELD) {
if (playerInventoryHolder.inventory().getItemInHand().asItem() == Items.SHIELD) {
useItem(Hand.MAIN_HAND);
} else if (playerInventory.getOffhand().asItem() == Items.SHIELD) {
} else if (playerInventoryHolder.inventory().getOffhand().asItem() == Items.SHIELD) {
useItem(Hand.OFF_HAND);
} else {
// No blocking
@ -1507,6 +1502,17 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
return true;
}
public @NonNull PlayerInventory getPlayerInventory() {
return this.playerInventoryHolder.inventory();
}
public @Nullable Inventory getOpenInventory() {
if (this.inventoryHolder == null) {
return null;
}
return this.inventoryHolder.inventory();
}
@Override
public boolean sendForm(@NonNull FormBuilder<?, ?, ?> formBuilder) {
formCache.showForm(formBuilder.build());

View file

@ -40,7 +40,7 @@ import org.geysermc.geyser.session.GeyserSession;
/**
* Provided as a base for any inventory that requires a block for opening it
*/
public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTranslator {
public abstract class AbstractBlockInventoryTranslator<Type extends Container> extends BaseInventoryTranslator<Type> {
private final InventoryHolder holder;
private final InventoryUpdater updater;
@ -78,43 +78,44 @@ public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTran
}
@Override
public boolean requiresOpeningDelay(GeyserSession session, Inventory inventory) {
return inventory instanceof Container container && !container.isUsingRealBlock();
public boolean requiresOpeningDelay(GeyserSession session, Type container) {
return !container.isUsingRealBlock();
}
@Override
public boolean canReuseInventory(GeyserSession session, @NonNull Inventory inventory, @NonNull Inventory previous) {
if (super.canReuseInventory(session, inventory, previous)
&& inventory instanceof Container container
&& previous instanceof Container previousContainer) {
public boolean canReuseInventory(GeyserSession session, @NonNull Inventory newInventory, @NonNull Inventory previous) {
if (super.canReuseInventory(session, newInventory, previous)
&& newInventory instanceof Container container
&& previous instanceof Container previousContainer
) {
return holder.canReuseContainer(session, container, previousContainer);
}
return false;
}
@Override
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
return holder.prepareInventory(session, (Container) inventory);
public boolean prepareInventory(GeyserSession session, Type container) {
return holder.prepareInventory(session, container);
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
holder.openInventory(session, (Container) inventory);
public void openInventory(GeyserSession session, Type container) {
holder.openInventory(session, container);
}
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
holder.closeInventory(session, (Container) inventory, closeContainerType(inventory));
public void closeInventory(GeyserSession session, Type container, boolean force) {
holder.closeInventory(session, container, closeContainerType(container));
}
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
updater.updateInventory(this, session, inventory);
public void updateInventory(GeyserSession session, Type container) {
updater.updateInventory(this, session, container);
}
@Override
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
updater.updateSlot(this, session, inventory, slot);
public void updateSlot(GeyserSession session, Type container, int slot) {
updater.updateSlot(this, session, container, slot);
}
/*
@ -122,5 +123,5 @@ public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTran
But only for some blocks! And some blocks only respond to specific container types (dispensers/droppers now require the specific type...)
When this returns null, we just... break the block, and replace it. Primitive. But if that works... fine.
*/
public abstract @Nullable ContainerType closeContainerType(Inventory inventory);
public abstract @Nullable ContainerType closeContainerType(Type container);
}

View file

@ -35,8 +35,6 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.response.ItemStackResponse;
import org.geysermc.geyser.inventory.AnvilContainer;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.updater.AnvilInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession;
@ -44,22 +42,21 @@ import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import java.util.Objects;
public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator {
public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator<AnvilContainer> {
public AnvilInventoryTranslator() {
super(3, Blocks.ANVIL, org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.ANVIL, AnvilInventoryUpdater.INSTANCE,
Blocks.CHIPPED_ANVIL, Blocks.DAMAGED_ANVIL);
}
@Override
protected boolean shouldHandleRequestFirst(ItemStackRequestAction action, Inventory inventory) {
protected boolean shouldHandleRequestFirst(ItemStackRequestAction action, AnvilContainer container) {
return action.getType() == ItemStackRequestActionType.CRAFT_RECIPE_OPTIONAL;
}
@Override
protected ItemStackResponse translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
protected ItemStackResponse translateSpecialRequest(GeyserSession session, AnvilContainer container, ItemStackRequest request) {
// Guarded by shouldHandleRequestFirst check
CraftRecipeOptionalAction data = (CraftRecipeOptionalAction) request.getActions()[0];
AnvilContainer container = (AnvilContainer) inventory;
if (request.getFilterStrings().length != 0) {
// Required as of 1.18.30 - FilterTextPackets no longer appear to be sent
@ -69,7 +66,7 @@ public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator {
}
}
return super.translateRequest(session, inventory, request);
return super.translateRequest(session, container, request);
}
@Override
@ -83,12 +80,12 @@ public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator {
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, AnvilContainer container) {
return switch (slot) {
case 0 -> new BedrockContainerSlot(ContainerSlotType.ANVIL_INPUT, 1);
case 1 -> new BedrockContainerSlot(ContainerSlotType.ANVIL_MATERIAL, 2);
case 2 -> new BedrockContainerSlot(ContainerSlotType.ANVIL_RESULT, 50);
default -> super.javaSlotToBedrockContainer(slot);
default -> super.javaSlotToBedrockContainer(slot, container);
};
}
@ -103,22 +100,21 @@ public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator {
}
@Override
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
return new AnvilContainer(session, name, windowId, this.size, containerType, playerInventory, this);
public AnvilContainer createInventory(GeyserSession session, String name, int windowId, ContainerType containerType) {
return new AnvilContainer(session, name, windowId, this.size, containerType);
}
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
public void updateProperty(GeyserSession session, AnvilContainer container, int key, int value) {
// The only property sent by Java is key 0 which is the level cost
if (key != 0) return;
AnvilContainer anvilContainer = (AnvilContainer) inventory;
anvilContainer.setJavaLevelCost(value);
anvilContainer.setUseJavaLevelCost(true);
updateSlot(session, anvilContainer, 1);
container.setJavaLevelCost(value);
container.setUseJavaLevelCost(true);
updateSlot(session, container, 1);
}
@Override
public org.cloudburstmc.protocol.bedrock.data.inventory.@Nullable ContainerType closeContainerType(Inventory inventory) {
public org.cloudburstmc.protocol.bedrock.data.inventory.@Nullable ContainerType closeContainerType(AnvilContainer container) {
return null;
}
}

View file

@ -27,17 +27,19 @@ package org.geysermc.geyser.translator.inventory;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData;
import org.geysermc.geyser.inventory.*;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.SlotType;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
public abstract class BaseInventoryTranslator extends InventoryTranslator {
public abstract class BaseInventoryTranslator<Type extends Container> extends InventoryTranslator<Type> {
public BaseInventoryTranslator(int size) {
super(size);
}
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
public void updateProperty(GeyserSession session, Type container, int key, int value) {
//
}
@ -72,7 +74,7 @@ public abstract class BaseInventoryTranslator extends InventoryTranslator {
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, Type inventory) {
if (slot >= this.size) {
final int tmp = slot - this.size;
if (tmp < 27) {
@ -90,7 +92,8 @@ public abstract class BaseInventoryTranslator extends InventoryTranslator {
}
@Override
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
return new Container(session, name, windowId, this.size, containerType, playerInventory, this);
public Type createInventory(GeyserSession session, String name, int windowId, ContainerType containerType) {
//noinspection unchecked
return (Type) new Container(session, name, windowId, this.size, containerType);
}
}

View file

@ -39,8 +39,6 @@ import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket;
import org.geysermc.geyser.inventory.BeaconContainer;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.holder.BlockInventoryHolder;
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
@ -52,7 +50,7 @@ import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.S
import java.util.OptionalInt;
public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator {
public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator<BeaconContainer> {
public BeaconInventoryTranslator() {
super(1, new BlockInventoryHolder(Blocks.BEACON, org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.BEACON) {
@Override
@ -73,28 +71,27 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
public void updateProperty(GeyserSession session, BeaconContainer container, int key, int value) {
//FIXME?: Beacon graphics look weird after inputting an item. This might be a Bedrock bug, since it resets to nothing
// on BDS
BeaconContainer beaconContainer = (BeaconContainer) inventory;
switch (key) {
case 0:
// Power - beacon doesn't use this, and uses the block position instead
break;
case 1:
beaconContainer.setPrimaryId(value == -1 ? 0 : value);
container.setPrimaryId(value == -1 ? 0 : value);
break;
case 2:
beaconContainer.setSecondaryId(value == -1 ? 0 : value);
container.setSecondaryId(value == -1 ? 0 : value);
break;
}
// Send a block entity data packet update to the fake beacon inventory
Vector3i position = inventory.getHolderPosition();
Vector3i position = container.getHolderPosition();
NbtMapBuilder builder = BlockEntityTranslator.getConstantBedrockTag("Beacon", position)
.putString("CustomName", inventory.getTitle())
.putInt("primary", beaconContainer.getPrimaryId())
.putInt("secondary", beaconContainer.getSecondaryId());
.putString("CustomName", container.getTitle())
.putInt("primary", container.getPrimaryId())
.putInt("secondary", container.getSecondaryId());
BlockEntityDataPacket packet = new BlockEntityDataPacket();
packet.setBlockPosition(position);
@ -103,17 +100,17 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
protected boolean shouldHandleRequestFirst(ItemStackRequestAction action, Inventory inventory) {
protected boolean shouldHandleRequestFirst(ItemStackRequestAction action, BeaconContainer container) {
return action.getType() == ItemStackRequestActionType.BEACON_PAYMENT;
}
@Override
public ItemStackResponse translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
public ItemStackResponse translateSpecialRequest(GeyserSession session, BeaconContainer container, ItemStackRequest request) {
// Input a beacon payment
BeaconPaymentAction beaconPayment = (BeaconPaymentAction) request.getActions()[0];
ServerboundSetBeaconPacket packet = new ServerboundSetBeaconPacket(toJava(beaconPayment.getPrimaryEffect()), toJava(beaconPayment.getSecondaryEffect()));
session.sendDownstreamGamePacket(packet);
return acceptRequest(request, makeContainerEntries(session, inventory, IntSets.emptySet()));
return acceptRequest(request, makeContainerEntries(session, container, IntSets.emptySet()));
}
private OptionalInt toJava(int effectChoice) {
@ -129,11 +126,11 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, BeaconContainer container) {
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.BEACON_PAYMENT, 27);
}
return super.javaSlotToBedrockContainer(slot);
return super.javaSlotToBedrockContainer(slot, container);
}
@Override
@ -145,12 +142,12 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
return new BeaconContainer(session, name, windowId, this.size, containerType, playerInventory, this);
public BeaconContainer createInventory(GeyserSession session, String name, int windowId, ContainerType containerType) {
return new BeaconContainer(session, name, windowId, this.size, containerType);
}
@Override
public org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType closeContainerType(Inventory inventory) {
public org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType closeContainerType(BeaconContainer container) {
return null;
}
}

View file

@ -31,13 +31,13 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData;
import org.cloudburstmc.protocol.bedrock.packet.ContainerSetDataPacket;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.session.GeyserSession;
public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator {
public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator<Container> {
public BrewingInventoryTranslator() {
super(5, Blocks.BREWING_STAND.defaultBlockState()
.withValue(Properties.HAS_BOTTLE_0, false)
@ -46,19 +46,19 @@ public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
super.openInventory(session, inventory);
public void openInventory(GeyserSession session, Container container) {
super.openInventory(session, container);
ContainerSetDataPacket dataPacket = new ContainerSetDataPacket();
dataPacket.setWindowId((byte) inventory.getBedrockId());
dataPacket.setWindowId((byte) container.getBedrockId());
dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_FUEL_TOTAL);
dataPacket.setValue(20);
session.sendUpstreamPacket(dataPacket);
}
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
public void updateProperty(GeyserSession session, Container container, int key, int value) {
ContainerSetDataPacket dataPacket = new ContainerSetDataPacket();
dataPacket.setWindowId((byte) inventory.getBedrockId());
dataPacket.setWindowId((byte) container.getBedrockId());
switch (key) {
case 0:
dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_BREW_TIME);
@ -99,17 +99,17 @@ public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, Container container) {
return switch (slot) {
case 0, 1, 2 -> new BedrockContainerSlot(ContainerSlotType.BREWING_RESULT, javaSlotToBedrock(slot));
case 3 -> new BedrockContainerSlot(ContainerSlotType.BREWING_INPUT, 0);
case 4 -> new BedrockContainerSlot(ContainerSlotType.BREWING_FUEL, 4);
default -> super.javaSlotToBedrockContainer(slot);
default -> super.javaSlotToBedrockContainer(slot, container);
};
}
@Override
public @Nullable ContainerType closeContainerType(Inventory inventory) {
public @Nullable ContainerType closeContainerType(Container container) {
return ContainerType.BREWING_STAND;
}
}

View file

@ -56,7 +56,7 @@ public final class BundleInventoryTranslator {
* @return a processed bundle interaction, or null to resume normal transaction handling.
*/
@Nullable
static ItemStackResponse handleBundle(GeyserSession session, InventoryTranslator translator, Inventory inventory, ItemStackRequest request, boolean sendCreativePackets) {
static <T extends Inventory> ItemStackResponse handleBundle(GeyserSession session, InventoryTranslator<T> translator, T inventory, ItemStackRequest request, boolean sendCreativePackets) {
TransferItemStackRequestAction action = null;
for (ItemStackRequestAction requestAction : request.getActions()) {
if (requestAction instanceof SwapAction swapAction) {

View file

@ -35,21 +35,21 @@ import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
public class CartographyInventoryTranslator extends AbstractBlockInventoryTranslator {
public class CartographyInventoryTranslator extends AbstractBlockInventoryTranslator<CartographyContainer> {
public CartographyInventoryTranslator() {
super(3, Blocks.CARTOGRAPHY_TABLE, org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.CARTOGRAPHY, UIInventoryUpdater.INSTANCE);
}
@Override
protected boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer,
protected boolean shouldRejectItemPlace(GeyserSession session, CartographyContainer container, ContainerSlotType bedrockSourceContainer,
int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) {
if (javaDestinationSlot == 0) {
// Bedrock Edition can use paper or an empty map in slot 0
GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot);
GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : container.getItem(javaSourceSlot);
return itemStack.asItem() == Items.PAPER || itemStack.asItem() == Items.MAP;
} else if (javaDestinationSlot == 1) {
// Bedrock Edition can use a compass to create locator maps, or use a filled map, in the ADDITIONAL slot
GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot);
GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : container.getItem(javaSourceSlot);
return itemStack.asItem() == Items.COMPASS || itemStack.asItem() == Items.FILLED_MAP;
}
return false;
@ -66,12 +66,12 @@ public class CartographyInventoryTranslator extends AbstractBlockInventoryTransl
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, CartographyContainer container) {
return switch (slot) {
case 0 -> new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_INPUT, 12);
case 1 -> new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_ADDITIONAL, 13);
case 2 -> new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_RESULT, 50);
default -> super.javaSlotToBedrockContainer(slot);
default -> super.javaSlotToBedrockContainer(slot, container);
};
}
@ -86,12 +86,12 @@ public class CartographyInventoryTranslator extends AbstractBlockInventoryTransl
}
@Override
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
return new CartographyContainer(session, name, windowId, this.size, containerType, playerInventory, this);
public CartographyContainer createInventory(GeyserSession session, String name, int windowId, ContainerType containerType) {
return new CartographyContainer(session, name, windowId, this.size, containerType);
}
@Override
public org.cloudburstmc.protocol.bedrock.data.inventory.@Nullable ContainerType closeContainerType(Inventory inventory) {
public org.cloudburstmc.protocol.bedrock.data.inventory.@Nullable ContainerType closeContainerType(CartographyContainer container) {
return null;
}
}

View file

@ -42,7 +42,7 @@ import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
* cannot be used to calculate the inventory slot indices. The Translator and the Updater must then
* override any methods that use the size for such calculations
*/
public class CrafterInventoryTranslator extends AbstractBlockInventoryTranslator {
public class CrafterInventoryTranslator extends AbstractBlockInventoryTranslator<CrafterContainer> {
public static final int JAVA_RESULT_SLOT = 45;
public static final int BEDROCK_RESULT_SLOT = 50;
@ -58,10 +58,8 @@ public class CrafterInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
public void updateProperty(GeyserSession session, CrafterContainer container, int key, int value) {
// the slot bits and triggered state are sent here rather than in a BlockEntityDataPacket. Yippee.
CrafterContainer container = (CrafterContainer) inventory;
if (key == TRIGGERED_KEY) {
container.setTriggered(value == TRIGGERED);
} else {
@ -111,7 +109,7 @@ public class CrafterInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot, CrafterContainer container) {
if (javaSlot == JAVA_RESULT_SLOT) {
return new BedrockContainerSlot(ContainerSlotType.CRAFTER_BLOCK_CONTAINER, BEDROCK_RESULT_SLOT);
}
@ -139,9 +137,9 @@ public class CrafterInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
public CrafterContainer createInventory(GeyserSession session, String name, int windowId, ContainerType containerType) {
// Java sends the triggered and slot bits incrementally through properties, which we store here
return new CrafterContainer(session, name, windowId, this.size, containerType, playerInventory, this);
return new CrafterContainer(session, name, windowId, this.size, containerType);
}
private static void updateBlockEntity(GeyserSession session, CrafterContainer container) {
@ -164,7 +162,7 @@ public class CrafterInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
public org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType closeContainerType(Inventory inventory) {
public org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType closeContainerType(CrafterContainer container) {
return null;
}
}

View file

@ -29,12 +29,12 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.SlotType;
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslator {
public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslator<Container> {
public CraftingInventoryTranslator() {
super(10, Blocks.CRAFTING_TABLE, ContainerType.WORKBENCH, UIInventoryUpdater.INSTANCE);
}
@ -53,14 +53,14 @@ public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslato
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, Container container) {
if (isCraftingGrid(slot)) {
return new BedrockContainerSlot(ContainerSlotType.CRAFTING_INPUT, slot + 31);
}
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.CRAFTING_OUTPUT, 0);
}
return super.javaSlotToBedrockContainer(slot);
return super.javaSlotToBedrockContainer(slot, container);
}
@Override
@ -89,7 +89,7 @@ public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslato
}
@Override
public ContainerType closeContainerType(Inventory inventory) {
public ContainerType closeContainerType(Container container) {
return null;
}
}

View file

@ -47,15 +47,14 @@ import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.S
import java.util.Arrays;
public class EnchantingInventoryTranslator extends AbstractBlockInventoryTranslator {
public class EnchantingInventoryTranslator extends AbstractBlockInventoryTranslator<EnchantingContainer> {
public EnchantingInventoryTranslator() {
super(2, Blocks.ENCHANTING_TABLE, org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.ENCHANTMENT, UIInventoryUpdater.INSTANCE);
}
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
public void updateProperty(GeyserSession session, EnchantingContainer container, int key, int value) {
int slotToUpdate;
EnchantingContainer enchantingInventory = (EnchantingContainer) inventory;
boolean shouldUpdate = false;
switch (key) {
case 0:
@ -63,7 +62,7 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla
case 2:
// Experience required
slotToUpdate = key;
enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setXpCost(value);
container.getGeyserEnchantOptions()[slotToUpdate].setXpCost(value);
break;
case 4:
case 5:
@ -83,45 +82,44 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla
bedrockIndex = -1;
}
}
enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setEnchantIndex(bedrockIndex);
container.getGeyserEnchantOptions()[slotToUpdate].setEnchantIndex(bedrockIndex);
break;
case 7:
case 8:
case 9:
// Enchantment level
slotToUpdate = key - 7;
enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setEnchantLevel(value);
container.getGeyserEnchantOptions()[slotToUpdate].setEnchantLevel(value);
shouldUpdate = true; // Java sends each property as its own packet, so let's only update after all properties have been sent
break;
default:
return;
}
GeyserEnchantOption enchantOption = enchantingInventory.getGeyserEnchantOptions()[slotToUpdate];
GeyserEnchantOption enchantOption = container.getGeyserEnchantOptions()[slotToUpdate];
if (shouldUpdate && enchantOption.hasChanged()) {
enchantingInventory.getEnchantOptions()[slotToUpdate] = enchantOption.build(session);
container.getEnchantOptions()[slotToUpdate] = enchantOption.build(session);
PlayerEnchantOptionsPacket packet = new PlayerEnchantOptionsPacket();
packet.getOptions().addAll(Arrays.asList(enchantingInventory.getEnchantOptions()));
packet.getOptions().addAll(Arrays.asList(container.getEnchantOptions()));
session.sendUpstreamPacket(packet);
}
}
@Override
protected boolean shouldHandleRequestFirst(ItemStackRequestAction action, Inventory inventory) {
protected boolean shouldHandleRequestFirst(ItemStackRequestAction action, EnchantingContainer container) {
return action.getType() == ItemStackRequestActionType.CRAFT_RECIPE;
}
@Override
public ItemStackResponse translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
public ItemStackResponse translateSpecialRequest(GeyserSession session, EnchantingContainer container, ItemStackRequest request) {
// Client has requested an item to be enchanted
CraftRecipeAction craftRecipeData = (CraftRecipeAction) request.getActions()[0];
EnchantingContainer enchantingInventory = (EnchantingContainer) inventory;
int javaSlot = -1;
for (int i = 0; i < enchantingInventory.getEnchantOptions().length; i++) {
EnchantOptionData enchantData = enchantingInventory.getEnchantOptions()[i];
for (int i = 0; i < container.getEnchantOptions().length; i++) {
EnchantOptionData enchantData = container.getEnchantOptions()[i];
if (enchantData != null) {
if (craftRecipeData.getRecipeNetworkId() == enchantData.getEnchantNetId()) {
// Enchant net ID is how we differentiate between what item Bedrock wants
javaSlot = enchantingInventory.getGeyserEnchantOptions()[i].getJavaIndex();
javaSlot = container.getGeyserEnchantOptions()[i].getJavaIndex();
break;
}
}
@ -130,9 +128,9 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla
// Slot should be determined as 0, 1, or 2
return rejectRequest(request);
}
ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getJavaId(), javaSlot);
ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(container.getJavaId(), javaSlot);
session.sendDownstreamGamePacket(packet);
return acceptRequest(request, makeContainerEntries(session, inventory, IntSets.emptySet()));
return acceptRequest(request, makeContainerEntries(session, container, IntSets.emptySet()));
}
@Override
@ -147,14 +145,14 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, EnchantingContainer container) {
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.ENCHANTING_INPUT, 14);
}
if (slot == 1) {
return new BedrockContainerSlot(ContainerSlotType.ENCHANTING_MATERIAL, 15);
}
return super.javaSlotToBedrockContainer(slot);
return super.javaSlotToBedrockContainer(slot, container);
}
@Override
@ -169,12 +167,12 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla
}
@Override
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
return new EnchantingContainer(session, name, windowId, this.size, containerType, playerInventory, this);
public EnchantingContainer createInventory(GeyserSession session, String name, int windowId, ContainerType containerType) {
return new EnchantingContainer(session, name, windowId, this.size, containerType);
}
@Override
public org.cloudburstmc.protocol.bedrock.data.inventory.@Nullable ContainerType closeContainerType(Inventory inventory) {
public org.cloudburstmc.protocol.bedrock.data.inventory.@Nullable ContainerType closeContainerType(EnchantingContainer container) {
return null;
}
}

View file

@ -29,8 +29,6 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Generic3X3Container;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession;
@ -39,39 +37,39 @@ import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
/**
* Droppers and dispensers
*/
public class Generic3X3InventoryTranslator extends AbstractBlockInventoryTranslator {
public class Generic3X3InventoryTranslator extends AbstractBlockInventoryTranslator<Generic3X3Container> {
public Generic3X3InventoryTranslator() {
super(9, Blocks.DISPENSER, org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.DISPENSER, ContainerInventoryUpdater.INSTANCE,
Blocks.DROPPER);
}
@Override
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
return new Generic3X3Container(session, name, windowId, this.size, containerType, playerInventory, this);
public Generic3X3Container createInventory(GeyserSession session, String name, int windowId, ContainerType containerType) {
return new Generic3X3Container(session, name, windowId, this.size, containerType);
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
public void openInventory(GeyserSession session, Generic3X3Container container) {
ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket();
containerOpenPacket.setId((byte) inventory.getBedrockId());
containerOpenPacket.setId((byte) container.getBedrockId());
// Required for opening the real block - otherwise, if the container type is incorrect, it refuses to open
containerOpenPacket.setType(((Generic3X3Container) inventory).isDropper() ? org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.DROPPER : org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.DISPENSER);
containerOpenPacket.setBlockPosition(inventory.getHolderPosition());
containerOpenPacket.setUniqueEntityId(inventory.getHolderId());
containerOpenPacket.setType(container.isDropper() ? org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.DROPPER : org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.DISPENSER);
containerOpenPacket.setBlockPosition(container.getHolderPosition());
containerOpenPacket.setUniqueEntityId(container.getHolderId());
session.sendUpstreamPacket(containerOpenPacket);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot, Generic3X3Container container) {
if (javaSlot < this.size) {
return new BedrockContainerSlot(ContainerSlotType.LEVEL_ENTITY, javaSlot);
}
return super.javaSlotToBedrockContainer(javaSlot);
return super.javaSlotToBedrockContainer(javaSlot, container);
}
@Override
public org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType closeContainerType(Inventory inventory) {
return ((Generic3X3Container) inventory).isDropper() ? org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.DROPPER :
public org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType closeContainerType(Generic3X3Container container) {
return container.isDropper() ? org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.DROPPER :
org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.DISPENSER;
}
}

View file

@ -29,11 +29,11 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
public class GrindstoneInventoryTranslator extends AbstractBlockInventoryTranslator {
public class GrindstoneInventoryTranslator extends AbstractBlockInventoryTranslator<Container> {
public GrindstoneInventoryTranslator() {
super(3, Blocks.GRINDSTONE, ContainerType.GRINDSTONE, UIInventoryUpdater.INSTANCE);
}
@ -49,12 +49,12 @@ public class GrindstoneInventoryTranslator extends AbstractBlockInventoryTransla
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, Container container) {
return switch (slot) {
case 0 -> new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_INPUT, 16);
case 1 -> new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_ADDITIONAL, 17);
case 2 -> new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_RESULT, 50);
default -> super.javaSlotToBedrockContainer(slot);
default -> super.javaSlotToBedrockContainer(slot, container);
};
}
@ -69,7 +69,7 @@ public class GrindstoneInventoryTranslator extends AbstractBlockInventoryTransla
}
@Override
public ContainerType closeContainerType(Inventory inventory) {
public ContainerType closeContainerType(Container container) {
return null;
}
}

View file

@ -29,28 +29,28 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
/**
* Implemented on top of any block that does not have special properties implemented
*/
public class HopperInventoryTranslator extends AbstractBlockInventoryTranslator {
public class HopperInventoryTranslator extends AbstractBlockInventoryTranslator<Container> {
public HopperInventoryTranslator() {
super(5, Blocks.HOPPER, ContainerType.HOPPER, ContainerInventoryUpdater.INSTANCE);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot, Container container) {
if (javaSlot < this.size) {
return new BedrockContainerSlot(ContainerSlotType.LEVEL_ENTITY, javaSlot);
}
return super.javaSlotToBedrockContainer(javaSlot);
return super.javaSlotToBedrockContainer(javaSlot, container);
}
@Override
public @Nullable ContainerType closeContainerType(Inventory inventory) {
public @Nullable ContainerType closeContainerType(Container container) {
return ContainerType.HOPPER;
}
}

View file

@ -92,10 +92,10 @@ import java.util.Objects;
import static org.geysermc.geyser.translator.inventory.BundleInventoryTranslator.isBundle;
@AllArgsConstructor
public abstract class InventoryTranslator {
public abstract class InventoryTranslator<Type extends Inventory> {
public static final InventoryTranslator PLAYER_INVENTORY_TRANSLATOR = new PlayerInventoryTranslator();
private static final Map<ContainerType, InventoryTranslator> INVENTORY_TRANSLATORS = new EnumMap<>(ContainerType.class) {
public static final InventoryTranslator<PlayerInventory> PLAYER_INVENTORY_TRANSLATOR = new PlayerInventoryTranslator();
private static final Map<ContainerType, InventoryTranslator<? extends Inventory>> INVENTORY_TRANSLATORS = new EnumMap<>(ContainerType.class) {
{
/* Chest UIs */
put(ContainerType.GENERIC_9X1, new SingleChestInventoryTranslator(9));
@ -138,7 +138,7 @@ public abstract class InventoryTranslator {
public final int size;
// Whether the inventory open should be delayed.
public boolean requiresOpeningDelay(GeyserSession session, Inventory inventory) {
public boolean requiresOpeningDelay(GeyserSession session, Type inventory) {
return false;
}
@ -147,42 +147,74 @@ public abstract class InventoryTranslator {
*/
public boolean canReuseInventory(GeyserSession session, @NonNull Inventory inventory, @NonNull Inventory previous) {
// Filter for mismatches that require a new inventory.
if (inventory.getContainerType() == null || previous.getContainerType() == null
|| !Objects.equals(inventory.getContainerType(), previous.getContainerType())
if (inventory.getContainerType() == null || previous.getContainerType() == null ||
!Objects.equals(inventory.getContainerType(), previous.getContainerType()) ||
!Objects.equals(inventory.getTitle(), previous.getTitle()) ||
inventory.getSize() != previous.getSize()
) {
GeyserImpl.getInstance().getLogger().debug(session, "Not reusing inventory (%s) due to type change! ", InventoryUtils.debugInventory(inventory));
return false;
}
if (inventory.getSize() != previous.getSize()) {
GeyserImpl.getInstance().getLogger().debug(session, "Not reusing inventory (%s) due to size change! ", InventoryUtils.debugInventory(inventory));
return false;
}
if (!Objects.equals(inventory.getTitle(), previous.getTitle())) {
GeyserImpl.getInstance().getLogger().debug(session, "Not reusing inventory (%s) due to title change! ", InventoryUtils.debugInventory(inventory));
return false;
}
if (previous.getHolderId() == -1 && previous.getHolderPosition() == Vector3i.ZERO) {
GeyserImpl.getInstance().getLogger().debug(session, "Not reusing inventory (%s) since the old was not initialized! ", InventoryUtils.debugInventory(inventory));
return false;
}
// We can likely reuse the inventory!
return true;
// Finally, ensure that the previous inventory has been initialized
return previous.getHolderId() != -1 || previous.getHolderPosition() != Vector3i.ZERO;
}
public abstract boolean prepareInventory(GeyserSession session, Inventory inventory);
public abstract void openInventory(GeyserSession session, Inventory inventory);
public abstract void closeInventory(GeyserSession session, Inventory inventory);
public abstract void updateProperty(GeyserSession session, Inventory inventory, int key, int value);
public abstract void updateInventory(GeyserSession session, Inventory inventory);
public abstract void updateSlot(GeyserSession session, Inventory inventory, int slot);
/**
* Prepares the inventory before opening it. Bedrock requires the inventory to "exist" before opening it - that can be
* either a real block (e.g. chest), or an entity (e.g. horse)
* @return whether the inventory was successfully prepared
*/
public abstract boolean prepareInventory(GeyserSession session, Type inventory);
/**
* Opens the previously prepared inventory.
*/
public abstract void openInventory(GeyserSession session, Type inventory);
/**
* Closes the inventory, and if necessary, cleans up the prepared inventory.
*/
public abstract void closeInventory(GeyserSession session, Type inventory, boolean force);
/**
* Updates a property in the inventory.
*/
public abstract void updateProperty(GeyserSession session, Type inventory, int key, int value);
/**
* Updates the inventory by re-sending items for all slots of the inventory.
*/
public abstract void updateInventory(GeyserSession session, Type inventory);
/**
* Updates a specific slot by re-sending the item.
*/
public abstract void updateSlot(GeyserSession session, Type inventory, int slot);
/**
* Converts the Bedrock slot to the corresponding Java slot.
*/
public abstract int bedrockSlotToJava(ItemStackRequestSlotData slotInfoData);
/**
* Converts a Java slot to the corresponding Bedrock slot.
*/
public abstract int javaSlotToBedrock(int javaSlot);
public abstract BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot);
/**
* Converts a Java slot to the corresponding Bedrock container and slot
*/
public abstract BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot, Type inventory);
/**
* Returns the slot type for a Java slot id
*/
public abstract SlotType getSlotType(int javaSlot);
public abstract Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory);
/**
* Creates a new inventory.
*/
public abstract Type createInventory(GeyserSession session, String name, int windowId, ContainerType containerType);
/**
* Used for crafting-related transactions. Will override in PlayerInventoryTranslator and CraftingInventoryTranslator.
@ -199,7 +231,7 @@ public abstract class InventoryTranslator {
*
* @return true if this transfer should be rejected
*/
protected boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer,
protected boolean shouldRejectItemPlace(GeyserSession session, Type inventory, ContainerSlotType bedrockSourceContainer,
int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) {
return false;
}
@ -208,18 +240,18 @@ public abstract class InventoryTranslator {
* Should be overrided if this request matches a certain criteria and shouldn't be treated normally.
* E.G. anvil renaming or enchanting
*/
protected boolean shouldHandleRequestFirst(ItemStackRequestAction action, Inventory inventory) {
protected boolean shouldHandleRequestFirst(ItemStackRequestAction action, Type inventory) {
return false;
}
/**
* If {@link #shouldHandleRequestFirst(ItemStackRequestAction, Inventory)} returns true, this will be called
*/
protected ItemStackResponse translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
protected ItemStackResponse translateSpecialRequest(GeyserSession session, Type inventory, ItemStackRequest request) {
return rejectRequest(request);
}
public final void translateRequests(GeyserSession session, Inventory inventory, List<ItemStackRequest> requests) {
public final void translateRequests(GeyserSession session, Type inventory, List<ItemStackRequest> requests) {
boolean refresh = false;
ItemStackResponsePacket responsePacket = new ItemStackResponsePacket();
for (ItemStackRequest request : requests) {
@ -261,7 +293,7 @@ public abstract class InventoryTranslator {
inventory.resetNextStateId();
}
public ItemStackResponse translateRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
public ItemStackResponse translateRequest(GeyserSession session, Type inventory, ItemStackRequest request) {
ClickPlan plan = new ClickPlan(session, this, inventory);
IntSet affectedSlots = new IntOpenHashSet();
int pendingOutput = 0;
@ -589,7 +621,7 @@ public abstract class InventoryTranslator {
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
}
public ItemStackResponse translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
public ItemStackResponse translateCraftingRequest(GeyserSession session, Type inventory, ItemStackRequest request) {
int resultSize = 0;
int timesCrafted;
CraftState craftState = CraftState.START;
@ -712,7 +744,7 @@ public abstract class InventoryTranslator {
return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots));
}
public ItemStackResponse translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
public ItemStackResponse translateAutoCraftingRequest(GeyserSession session, Type inventory, ItemStackRequest request) {
final int gridSize = getGridSize();
if (gridSize == -1) {
return rejectRequest(request);
@ -899,7 +931,7 @@ public abstract class InventoryTranslator {
/**
* Handled in {@link PlayerInventoryTranslator}
*/
protected ItemStackResponse translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
protected ItemStackResponse translateCreativeRequest(GeyserSession session, Type inventory, ItemStackRequest request) {
return rejectRequest(request);
}
@ -961,14 +993,14 @@ public abstract class InventoryTranslator {
/**
* Print out the contents of an ItemStackRequest, should the net ID check fail.
*/
protected void dumpStackRequestDetails(GeyserSession session, Inventory inventory, ItemStackRequestSlotData source, ItemStackRequestSlotData destination) {
protected void dumpStackRequestDetails(GeyserSession session, Type inventory, ItemStackRequestSlotData source, ItemStackRequestSlotData destination) {
session.getGeyser().getLogger().error("Source: " + source.toString() + " Result: " + checkNetId(session, inventory, source));
session.getGeyser().getLogger().error("Destination: " + destination.toString() + " Result: " + checkNetId(session, inventory, destination));
session.getGeyser().getLogger().error("Geyser's record of source slot: " + inventory.getItem(bedrockSlotToJava(source)));
session.getGeyser().getLogger().error("Geyser's record of destination slot: " + inventory.getItem(bedrockSlotToJava(destination)));
}
public boolean checkNetId(GeyserSession session, Inventory inventory, ItemStackRequestSlotData slotInfoData) {
public boolean checkNetId(GeyserSession session, Type inventory, ItemStackRequestSlotData slotInfoData) {
if (BundleInventoryTranslator.isBundle(slotInfoData)) {
// Will thoroughly be investigated, if needed, in bundle checks.
return true;
@ -1050,13 +1082,13 @@ public abstract class InventoryTranslator {
return -1;
}
protected final List<ItemStackResponseContainer> makeContainerEntries(GeyserSession session, Inventory inventory, IntSet affectedSlots) {
protected final List<ItemStackResponseContainer> makeContainerEntries(GeyserSession session, Type inventory, IntSet affectedSlots) {
Map<ContainerSlotType, List<ItemStackResponseSlot>> containerMap = new HashMap<>();
// Manually call iterator to prevent Integer boxing
IntIterator it = affectedSlots.iterator();
while (it.hasNext()) {
int slot = it.nextInt();
BedrockContainerSlot bedrockSlot = javaSlotToBedrockContainer(slot);
BedrockContainerSlot bedrockSlot = javaSlotToBedrockContainer(slot, inventory);
List<ItemStackResponseSlot> list = containerMap.computeIfAbsent(bedrockSlot.container(), k -> new ArrayList<>());
list.add(makeItemEntry(bedrockSlot.slot(), inventory.getItem(slot)));
}
@ -1102,7 +1134,7 @@ public abstract class InventoryTranslator {
* @return the InventoryType for the given ContainerType.
*/
@Nullable
public static InventoryTranslator inventoryTranslator(@Nullable ContainerType type) {
public static InventoryTranslator<? extends Inventory> inventoryTranslator(@Nullable ContainerType type) {
if (type == null) {
return PLAYER_INVENTORY_TRANSLATOR;
}

View file

@ -32,9 +32,8 @@ 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.Inventory;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.geyser.inventory.LecternContainer;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.level.block.property.Properties;
@ -48,7 +47,9 @@ import org.geysermc.mcprotocollib.protocol.data.game.item.component.WritableBook
import org.geysermc.mcprotocollib.protocol.data.game.item.component.WrittenBookContent;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerButtonClickPacket;
public class LecternInventoryTranslator extends AbstractBlockInventoryTranslator {
import java.util.concurrent.TimeUnit;
public class LecternInventoryTranslator extends AbstractBlockInventoryTranslator<LecternContainer> {
/**
* Hack: Java opens a lectern first, and then follows it up with a ClientboundContainerSetContentPacket
@ -57,15 +58,15 @@ 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
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
super.prepareInventory(session, inventory);
if (((LecternContainer) inventory).isBookInPlayerInventory()) {
public boolean prepareInventory(GeyserSession session, LecternContainer container) {
super.prepareInventory(session, container);
if (container.isBookInPlayerInventory()) {
// See JavaOpenBookTranslator; this isn't a lectern but a book in the player inventory
updateBook(session, inventory, inventory.getItem(0));
updateBook(session, container, container.getItem(0));
receivedBook = true;
} else {
receivedBook = false; // We have to wait until we get the book
@ -74,95 +75,103 @@ public class LecternInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
public void openInventory(GeyserSession session, LecternContainer container) {
// Hacky, but we're dealing with LECTERNS! It cannot not be hacky.
// "initialized" indicates whether we've received the book from the Java server yet.
// dropping lectern book is the fun workaround when we have to enter the gui to drop the book.
// Since we leave it immediately... don't open it!
if (receivedBook && !session.isDroppingLecternBook()) {
super.openInventory(session, inventory);
super.openInventory(session, container);
}
}
// Lecterns don't require a delay before opening.
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
public boolean requiresOpeningDelay(GeyserSession session, LecternContainer container) {
return false;
}
@Override
public void closeInventory(GeyserSession session, LecternContainer container, boolean force) {
// 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!
LecternContainer lecternContainer = (LecternContainer) inventory;
Vector3i position = lecternContainer.isUsingRealBlock() ? session.getLastInteractionBlockPosition() : inventory.getHolderPosition();
Vector3i position = container.getHolderPosition();
var baseLecternTag = LecternUtils.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 0);
BlockEntityUtils.updateBlockEntity(session, baseLecternTag.build(), position);
// Closing lecterns isn't followed up by a ContainerClosePacket, so this wouldn't ever be reset.
session.setPendingOrCurrentBedrockInventoryId(-1);
super.closeInventory(session, inventory); // Removes the fake blocks if need be
super.closeInventory(session, container, force); // Removes the fake blocks if need be
// Now: Restore the lectern, if it actually exists
if (lecternContainer.isUsingRealBlock()) {
boolean hasBook = session.getGeyser().getWorldManager().blockAt(session, position).getValue(Properties.HAS_BOOK, false);
NbtMap map = LecternBlock.getBaseLecternTag(position, hasBook);
BlockEntityUtils.updateBlockEntity(session, map, position);
}
}
if (container.isUsingRealBlock()) {
Runnable closeLecternRunnable = () -> {
boolean hasBook = session.getGeyser().getWorldManager().blockAt(session, position).getValue(Properties.HAS_BOOK, false);
NbtMap map = LecternBlock.getBaseLecternTag(position, hasBook);
BlockEntityUtils.updateBlockEntity(session, map, position);
};
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
if (key == 0) { // Lectern page update
LecternContainer lecternContainer = (LecternContainer) inventory;
lecternContainer.setCurrentBedrockPage(value / 2);
lecternContainer.setBlockEntityTag(lecternContainer.getBlockEntityTag().toBuilder().putInt("page", lecternContainer.getCurrentBedrockPage()).build());
BlockEntityUtils.updateBlockEntity(session, lecternContainer.getBlockEntityTag(), lecternContainer.getPosition());
}
}
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
GeyserItemStack itemStack = inventory.getItem(0);
if (!itemStack.isEmpty()) {
boolean isDropping = session.isDroppingLecternBook();
updateBook(session, inventory, itemStack);
if (!receivedBook && !isDropping) {
receivedBook = true;
openInventory(session, inventory);
if (force) {
// Without a delay, an inventory close request can *occasionally* be ignored as we're restoring the book too quickly
session.scheduleInEventLoop(closeLecternRunnable, 100, TimeUnit.MILLISECONDS);
} else {
closeLecternRunnable.run();
}
}
}
@Override
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
// If we're not in a real lectern, the Java server thinks we are still in the player inventory.
if (((LecternContainer) inventory).isBookInPlayerInventory()) {
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, session.getPlayerInventory(), slot);
return;
}
super.updateSlot(session, inventory, slot);
if (slot == 0) {
updateBook(session, inventory, inventory.getItem(0));
public void updateProperty(GeyserSession session, LecternContainer container, int key, int value) {
if (key == 0) { // Lectern page update
container.setCurrentBedrockPage(value / 2);
container.setBlockEntityTag(container.getBlockEntityTag().toBuilder().putInt("page", container.getCurrentBedrockPage()).build());
BlockEntityUtils.updateBlockEntity(session, container.getBlockEntityTag(), container.getHolderPosition());
}
}
@Override
public org.cloudburstmc.protocol.bedrock.data.inventory.@Nullable ContainerType closeContainerType(Inventory inventory) {
public void updateInventory(GeyserSession session, LecternContainer container) {
GeyserItemStack itemStack = container.getItem(0);
if (!itemStack.isEmpty()) {
boolean isDropping = session.isDroppingLecternBook();
updateBook(session, container, itemStack);
if (!receivedBook && !isDropping) {
receivedBook = true;
openInventory(session, container);
}
}
}
@Override
public void updateSlot(GeyserSession session, LecternContainer container, int slot) {
super.updateSlot(session, container, slot);
if (slot == 0) {
updateBook(session, container, container.getItem(0));
}
}
@Override
public org.cloudburstmc.protocol.bedrock.data.inventory.@Nullable ContainerType closeContainerType(LecternContainer container) {
return null;
}
/**
* Translate the data of the book in the lectern into a block entity tag.
*/
private void updateBook(GeyserSession session, Inventory inventory, GeyserItemStack book) {
LecternContainer lecternContainer = (LecternContainer) inventory;
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 (lecternContainer.getBlockEntityTag() == null) {
Vector3i position = lecternContainer.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.closeInventory(session, container.getJavaId(), false);
}
} else if (container.getBlockEntityTag() == null) {
Vector3i position = container.getHolderPosition();
NbtMap blockEntityTag;
if (book.hasNonBaseComponents()) {
@ -185,24 +194,21 @@ public class LecternInventoryTranslator extends AbstractBlockInventoryTranslator
.putString("Name", "minecraft:written_book")
.putCompound("tag", itemData.getTag())
.build());
lecternTag.putInt("page", lecternContainer.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
lecternContainer.setBlockEntityTag(blockEntityTag);
lecternContainer.setPosition(position);
container.setBlockEntityTag(blockEntityTag);
BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position);
}
}
@Override
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
return new LecternContainer(session, name, windowId, this.size + playerInventory.getSize(), containerType, playerInventory, this);
public LecternContainer createInventory(GeyserSession session, String name, int windowId, ContainerType containerType) {
return new LecternContainer(session, name, windowId, this.size, containerType);
}
}

View file

@ -40,8 +40,8 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.response.ItemStackResponse;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.SlotType;
import org.geysermc.geyser.inventory.item.BannerPattern;
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
@ -58,7 +58,7 @@ import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.S
import java.util.ArrayList;
import java.util.List;
public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator {
public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator<Container> {
private static final Tag<BannerPattern> NO_ITEMS_REQUIRED = new Tag<>(JavaRegistries.BANNER_PATTERN, Key.key("no_item_required"));
@ -67,12 +67,12 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator {
}
@Override
protected boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer,
protected boolean shouldRejectItemPlace(GeyserSession session, Container container, ContainerSlotType bedrockSourceContainer,
int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) {
if (javaDestinationSlot != 1) {
return false;
}
GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot);
GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : container.getItem(javaSourceSlot);
if (itemStack.isEmpty()) {
return false;
}
@ -82,13 +82,13 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator {
}
@Override
protected boolean shouldHandleRequestFirst(ItemStackRequestAction action, Inventory inventory) {
protected boolean shouldHandleRequestFirst(ItemStackRequestAction action, Container container) {
// If the LOOM_MATERIAL slot is empty, we are crafting a pattern that does not come from an item
return action.getType() == ItemStackRequestActionType.CRAFT_LOOM && inventory.getItem(2).isEmpty();
return action.getType() == ItemStackRequestActionType.CRAFT_LOOM && container.getItem(2).isEmpty();
}
@Override
public ItemStackResponse translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
public ItemStackResponse translateSpecialRequest(GeyserSession session, Container container, ItemStackRequest request) {
ItemStackRequestAction headerData = request.getActions()[0];
ItemStackRequestAction data = request.getActions()[1];
if (!(headerData instanceof CraftLoomAction)) {
@ -119,10 +119,10 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator {
// Java's formula: 4 * row + col
// And the Java loom window has a fixed row/width of four
// So... Number / 4 = row (so we don't have to bother there), and number % 4 is our column, which leads us back to our index. :)
ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getJavaId(), index);
ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(container.getJavaId(), index);
session.sendDownstreamGamePacket(packet);
GeyserItemStack inputCopy = inventory.getItem(0).copy(1);
GeyserItemStack inputCopy = container.getItem(0).copy(1);
inputCopy.setNetId(session.getNextItemNetId());
BannerPatternLayer bannerPatternLayer = BannerItem.getJavaBannerPattern(session, pattern); // TODO
if (bannerPatternLayer != null) {
@ -132,9 +132,9 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator {
}
// Set the new item as the output
inventory.setItem(3, inputCopy, session);
container.setItem(3, inputCopy, session);
return translateRequest(session, inventory, request);
return translateRequest(session, container, request);
}
@Override
@ -149,13 +149,13 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator {
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, Container container) {
return switch (slot) {
case 0 -> new BedrockContainerSlot(ContainerSlotType.LOOM_INPUT, 9);
case 1 -> new BedrockContainerSlot(ContainerSlotType.LOOM_DYE, 10);
case 2 -> new BedrockContainerSlot(ContainerSlotType.LOOM_MATERIAL, 11);
case 3 -> new BedrockContainerSlot(ContainerSlotType.LOOM_RESULT, 50);
default -> super.javaSlotToBedrockContainer(slot);
default -> super.javaSlotToBedrockContainer(slot, container);
};
}
@ -179,7 +179,7 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator {
}
@Override
public @Nullable ContainerType closeContainerType(Inventory inventory) {
public @Nullable ContainerType closeContainerType(Container container) {
return null;
}
}

View file

@ -47,7 +47,7 @@ import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.S
import java.util.concurrent.TimeUnit;
public class MerchantInventoryTranslator extends BaseInventoryTranslator {
public class MerchantInventoryTranslator extends BaseInventoryTranslator<MerchantContainer> {
private final InventoryUpdater updater;
public MerchantInventoryTranslator() {
@ -66,12 +66,12 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator {
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, MerchantContainer container) {
return switch (slot) {
case 0 -> new BedrockContainerSlot(ContainerSlotType.TRADE2_INGREDIENT_1, 4);
case 1 -> new BedrockContainerSlot(ContainerSlotType.TRADE2_INGREDIENT_2, 5);
case 2 -> new BedrockContainerSlot(ContainerSlotType.TRADE2_RESULT, 50);
default -> super.javaSlotToBedrockContainer(slot);
default -> super.javaSlotToBedrockContainer(slot, container);
};
}
@ -94,9 +94,8 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator {
}
@Override
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
MerchantContainer merchantInventory = (MerchantContainer) inventory;
if (merchantInventory.getVillager() == null) {
public boolean prepareInventory(GeyserSession session, MerchantContainer container) {
if (container.getVillager() == null) {
long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet();
Vector3f pos = session.getPlayerEntity().getPosition().sub(0, 3, 0);
@ -115,66 +114,63 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator {
linkPacket.setEntityLink(new EntityLinkData(session.getPlayerEntity().getGeyserId(), geyserId, type, true, false, 0f));
session.sendUpstreamPacket(linkPacket);
merchantInventory.setVillager(villager);
container.setVillager(villager);
}
return true;
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
public void openInventory(GeyserSession session, MerchantContainer container) {
//Handled in JavaMerchantOffersTranslator
//TODO: send a blank inventory here in case the villager doesn't send a TradeList packet
}
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
MerchantContainer merchantInventory = (MerchantContainer) inventory;
if (merchantInventory.getVillager() != null) {
merchantInventory.getVillager().despawnEntity();
public void closeInventory(GeyserSession session, MerchantContainer container, boolean force) {
if (container.getVillager() != null) {
container.getVillager().despawnEntity();
}
}
@Override
public ItemStackResponse translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
public ItemStackResponse translateCraftingRequest(GeyserSession session, MerchantContainer container, ItemStackRequest request) {
// Behavior as of 1.18.10.
// We set the net ID to the trade index + 1. This doesn't appear to cause issues and means we don't have to
// store a map of net ID to trade index on our end.
int tradeChoice = ((CraftRecipeAction) request.getActions()[0]).getRecipeNetworkId() - 1;
return handleTrade(session, inventory, request, tradeChoice);
return handleTrade(session, container, request, tradeChoice);
}
@Override
public ItemStackResponse translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
public ItemStackResponse translateAutoCraftingRequest(GeyserSession session, MerchantContainer container, ItemStackRequest request) {
// 1.18.10 update - seems impossible to call without consoles/controller input
// We set the net ID to the trade index + 1. This doesn't appear to cause issues and means we don't have to
// store a map of net ID to trade index on our end.
int tradeChoice = ((AutoCraftRecipeAction) request.getActions()[0]).getRecipeNetworkId() - 1;
return handleTrade(session, inventory, request, tradeChoice);
return handleTrade(session, container, request, tradeChoice);
}
private ItemStackResponse handleTrade(GeyserSession session, Inventory inventory, ItemStackRequest request, int tradeChoice) {
private ItemStackResponse handleTrade(GeyserSession session, MerchantContainer container, ItemStackRequest request, int tradeChoice) {
ServerboundSelectTradePacket packet = new ServerboundSelectTradePacket(tradeChoice);
session.sendDownstreamGamePacket(packet);
if (session.isEmulatePost1_13Logic()) {
// 1.18 Java cooperates nicer than older versions
if (inventory instanceof MerchantContainer merchantInventory) {
merchantInventory.onTradeSelected(session, tradeChoice);
}
return translateRequest(session, inventory, request);
container.onTradeSelected(session, tradeChoice);
return translateRequest(session, container, request);
} else {
// 1.18 servers works fine without a workaround, but ViaVersion needs to work around 1.13 servers,
// so we need to work around that with the delay. Specifically they force a window refresh after a
// trade packet has been sent.
session.scheduleInEventLoop(() -> {
if (inventory instanceof MerchantContainer merchantInventory) {
if (session.getOpenInventory() instanceof MerchantContainer merchantInventory) {
merchantInventory.onTradeSelected(session, tradeChoice);
// Ignore output since we don't want to send a delayed response packet back to the client
translateRequest(session, inventory, request);
translateRequest(session, container, request);
// Resync items once more
updateInventory(session, inventory);
updateInventory(session, container);
InventoryUtils.updateCursor(session);
}
}, 100, TimeUnit.MILLISECONDS);
@ -185,17 +181,17 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator {
}
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
updater.updateInventory(this, session, inventory);
public void updateInventory(GeyserSession session, MerchantContainer container) {
updater.updateInventory(this, session, container);
}
@Override
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
updater.updateSlot(this, session, inventory, slot);
public void updateSlot(GeyserSession session, MerchantContainer container, int slot) {
updater.updateSlot(this, session, container, slot);
}
@Override
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
return new MerchantContainer(session, name, windowId, this.size, containerType, playerInventory, this);
public MerchantContainer createInventory(GeyserSession session, String name, int windowId, ContainerType containerType) {
return new MerchantContainer(session, name, windowId, this.size, containerType);
}
}

View file

@ -40,7 +40,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.response.ItemStackResponse;
import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession;
@ -52,7 +52,7 @@ import java.util.function.IntFunction;
* Translator for smithing tables for pre-1.20 servers.
* This adapts ViaVersion's furnace ui to the 1.20+ smithing table; with the addition of a fake smithing template so Bedrock clients can use it.
*/
public class OldSmithingTableTranslator extends AbstractBlockInventoryTranslator {
public class OldSmithingTableTranslator extends AbstractBlockInventoryTranslator<Container> {
public static final OldSmithingTableTranslator INSTANCE = new OldSmithingTableTranslator();
@ -73,12 +73,12 @@ public class OldSmithingTableTranslator extends AbstractBlockInventoryTranslator
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, Container container) {
return switch (slot) {
case 0 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
case 1 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
case 2 -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
default -> super.javaSlotToBedrockContainer(slot);
default -> super.javaSlotToBedrockContainer(slot, container);
};
}
@ -93,12 +93,12 @@ public class OldSmithingTableTranslator extends AbstractBlockInventoryTranslator
}
@Override
public boolean shouldHandleRequestFirst(ItemStackRequestAction action, Inventory inventory) {
public boolean shouldHandleRequestFirst(ItemStackRequestAction action, Container container) {
return true;
}
@Override
protected ItemStackResponse translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
protected ItemStackResponse translateSpecialRequest(GeyserSession session, Container container, ItemStackRequest request) {
for (var action: request.getActions()) {
switch (action.getType()) {
case DROP -> {
@ -127,7 +127,7 @@ public class OldSmithingTableTranslator extends AbstractBlockInventoryTranslator
}
}
// Allow everything else that doesn't involve the fake template
return super.translateRequest(session, inventory, request);
return super.translateRequest(session, container, request);
}
private boolean isInvalidAction(ItemStackRequestSlotData slotData) {
@ -135,8 +135,8 @@ public class OldSmithingTableTranslator extends AbstractBlockInventoryTranslator
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
super.openInventory(session, inventory);
public void openInventory(GeyserSession session, Container container) {
super.openInventory(session, container);
// pre-1.20 server has no concept of templates, but we are working with a 1.20 client
// put a fake netherite upgrade template in the template slot otherwise the client doesn't recognize a valid recipe
@ -148,7 +148,7 @@ public class OldSmithingTableTranslator extends AbstractBlockInventoryTranslator
}
@Override
public @Nullable ContainerType closeContainerType(Inventory inventory) {
public @Nullable ContainerType closeContainerType(Container container) {
return null;
}
}

View file

@ -71,7 +71,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.IntFunction;
public class PlayerInventoryTranslator extends InventoryTranslator {
public class PlayerInventoryTranslator extends InventoryTranslator<PlayerInventory> {
private static final IntFunction<ItemData> UNUSUABLE_CRAFTING_SPACE_BLOCK = InventoryUtils.createUnusableSpaceBlock(GeyserLocale.getLocaleStringLog("geyser.inventory.unusable_item.creative"));
public PlayerInventoryTranslator() {
@ -84,7 +84,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
}
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
public void updateInventory(GeyserSession session, PlayerInventory inventory) {
updateCraftingGrid(session, inventory);
InventoryContentPacket inventoryContentPacket = new InventoryContentPacket();
@ -129,7 +129,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
* @param session Connection of the player
* @param inventory Inventory of the player
*/
public static void updateCraftingGrid(GeyserSession session, Inventory inventory) {
public static void updateCraftingGrid(GeyserSession session, PlayerInventory inventory) {
// Crafting grid
for (int i = 1; i < 5; i++) {
InventorySlotPacket slotPacket = new InventorySlotPacket();
@ -147,7 +147,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
}
@Override
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
public void updateSlot(GeyserSession session, PlayerInventory inventory, int slot) {
GeyserItemStack javaItem = inventory.getItem(slot);
ItemData bedrockItem = javaItem.getItemData(session);
@ -227,7 +227,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, PlayerInventory inventory) {
if (slot >= 36 && slot <= 44) {
return new BedrockContainerSlot(ContainerSlotType.HOTBAR, slot - 36);
} else if (slot >= 9 && slot <= 35) {
@ -253,7 +253,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
}
@Override
public ItemStackResponse translateRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
public ItemStackResponse translateRequest(GeyserSession session, PlayerInventory inventory, ItemStackRequest request) {
if (session.getGameMode() != GameMode.CREATIVE) {
return super.translateRequest(session, inventory, request);
}
@ -438,7 +438,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
}
@Override
protected ItemStackResponse translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
protected ItemStackResponse translateCreativeRequest(GeyserSession session, PlayerInventory inventory, ItemStackRequest request) {
ItemStack javaCreativeItem = null;
boolean bundle = false;
IntSet affectedSlots = new IntOpenHashSet();
@ -570,7 +570,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
}
@Override
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
public PlayerInventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType) {
throw new UnsupportedOperationException();
}
@ -580,12 +580,12 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
}
@Override
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
public boolean prepareInventory(GeyserSession session, PlayerInventory inventory) {
return true;
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
public void openInventory(GeyserSession session, PlayerInventory inventory) {
ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket();
containerOpenPacket.setId((byte) 0);
containerOpenPacket.setType(org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.INVENTORY);
@ -595,9 +595,8 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
}
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
if (session.isServerRequestedClosePlayerInventory()) {
session.setServerRequestedClosePlayerInventory(false);
public void closeInventory(GeyserSession session, PlayerInventory inventory, boolean force) {
if (force) {
Vector3i pos = session.getPlayerEntity().getPosition().toInt();
UpdateBlockPacket packet = new UpdateBlockPacket();
@ -617,6 +616,6 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
}
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
public void updateProperty(GeyserSession session, PlayerInventory inventory, int key, int value) {
}
}

View file

@ -33,6 +33,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.holder.BlockInventoryHolder;
import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater;
@ -45,7 +46,7 @@ import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.level.block.BlockEntityType;
public class ShulkerInventoryTranslator extends AbstractBlockInventoryTranslator {
public class ShulkerInventoryTranslator extends AbstractBlockInventoryTranslator<Container> {
public ShulkerInventoryTranslator() {
// Ensure that the shulker box default state won't be trying to open in a state facing the player
super(27, new BlockInventoryHolder(Blocks.SHULKER_BOX.defaultBlockState().withValue(Properties.FACING, Direction.NORTH), ContainerType.CONTAINER) {
@ -75,15 +76,15 @@ public class ShulkerInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot, Container container) {
if (javaSlot < this.size) {
return new BedrockContainerSlot(ContainerSlotType.SHULKER_BOX, javaSlot);
}
return super.javaSlotToBedrockContainer(javaSlot);
return super.javaSlotToBedrockContainer(javaSlot, container);
}
@Override
public @Nullable ContainerType closeContainerType(Inventory inventory) {
public @Nullable ContainerType closeContainerType(Container container) {
return ContainerType.CONTAINER;
}
}

View file

@ -29,11 +29,11 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslator {
public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslator<Container> {
public static final int TEMPLATE = 0;
public static final int INPUT = 1;
public static final int MATERIAL = 2;
@ -55,13 +55,13 @@ public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslato
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, Container container) {
return switch (slot) {
case TEMPLATE -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_TEMPLATE, 53);
case INPUT -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51);
case MATERIAL -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52);
case OUTPUT -> new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50);
default -> super.javaSlotToBedrockContainer(slot);
default -> super.javaSlotToBedrockContainer(slot, container);
};
}
@ -77,7 +77,7 @@ public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslato
}
@Override
public ContainerType closeContainerType(Inventory inventory) {
public ContainerType closeContainerType(Container container) {
return null;
}
}

View file

@ -42,18 +42,18 @@ import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerButtonClickPacket;
public class StonecutterInventoryTranslator extends AbstractBlockInventoryTranslator {
public class StonecutterInventoryTranslator extends AbstractBlockInventoryTranslator<StonecutterContainer> {
public StonecutterInventoryTranslator() {
super(2, Blocks.STONECUTTER, org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.STONECUTTER, UIInventoryUpdater.INSTANCE);
}
@Override
protected boolean shouldHandleRequestFirst(ItemStackRequestAction action, Inventory inventory) {
protected boolean shouldHandleRequestFirst(ItemStackRequestAction action, StonecutterContainer container) {
return action.getType() == ItemStackRequestActionType.CRAFT_RECIPE;
}
@Override
protected ItemStackResponse translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) {
protected ItemStackResponse translateSpecialRequest(GeyserSession session, StonecutterContainer container, ItemStackRequest request) {
// Guarded by shouldHandleRequestFirst
CraftRecipeAction data = (CraftRecipeAction) request.getActions()[0];
@ -63,24 +63,23 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl
return rejectRequest(request);
}
StonecutterContainer container = (StonecutterContainer) inventory;
ItemStack javaOutput = craftingData.output();
int button = craftingData.buttonId();
// If we've already pressed the button with this item, no need to press it again!
if (container.getStonecutterButton() != button) {
// Getting the index of the item in the Java stonecutter list
ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(inventory.getJavaId(), button);
ServerboundContainerButtonClickPacket packet = new ServerboundContainerButtonClickPacket(container.getJavaId(), button);
session.sendDownstreamGamePacket(packet);
container.setStonecutterButton(button);
}
if (inventory.getItem(1).getJavaId() != javaOutput.getId()) {
if (container.getItem(1).getJavaId() != javaOutput.getId()) {
// We don't know there is an output here, so we tell ourselves that there is
inventory.setItem(1, GeyserItemStack.from(javaOutput), session);
container.setItem(1, GeyserItemStack.from(javaOutput), session);
}
return translateRequest(session, inventory, request);
return translateRequest(session, container, request);
}
@Override
@ -93,14 +92,14 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, StonecutterContainer container) {
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.STONECUTTER_INPUT, 3);
}
if (slot == 1) {
return new BedrockContainerSlot(ContainerSlotType.STONECUTTER_RESULT, 50);
}
return super.javaSlotToBedrockContainer(slot);
return super.javaSlotToBedrockContainer(slot, container);
}
@Override
@ -123,12 +122,12 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl
}
@Override
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
return new StonecutterContainer(session, name, windowId, this.size, containerType, playerInventory, this);
public StonecutterContainer createInventory(GeyserSession session, String name, int windowId, ContainerType containerType) {
return new StonecutterContainer(session, name, windowId, this.size, containerType);
}
@Override
public org.cloudburstmc.protocol.bedrock.data.inventory.@Nullable ContainerType closeContainerType(Inventory inventory) {
public org.cloudburstmc.protocol.bedrock.data.inventory.@Nullable ContainerType closeContainerType(StonecutterContainer container) {
return org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType.STONECUTTER;
}
}

View file

@ -28,13 +28,12 @@ package org.geysermc.geyser.translator.inventory.chest;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.updater.ChestInventoryUpdater;
import org.geysermc.geyser.inventory.updater.InventoryUpdater;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.BaseInventoryTranslator;
public abstract class ChestInventoryTranslator extends BaseInventoryTranslator {
public abstract class ChestInventoryTranslator<Type extends Container> extends BaseInventoryTranslator<Type> {
private final InventoryUpdater updater;
public ChestInventoryTranslator(int size, int paddedSize) {
@ -43,35 +42,43 @@ public abstract class ChestInventoryTranslator extends BaseInventoryTranslator {
}
@Override
protected boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer,
protected boolean shouldRejectItemPlace(GeyserSession session, Type container, ContainerSlotType bedrockSourceContainer,
int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) {
// Reject any item placements that occur in the unusable inventory space
if (bedrockSourceContainer == ContainerSlotType.LEVEL_ENTITY && javaSourceSlot >= this.size) {
if (bedrockSourceContainer == slotType(container) && javaSourceSlot >= this.size) {
return true;
}
return bedrockDestinationContainer == ContainerSlotType.LEVEL_ENTITY && javaDestinationSlot >= this.size;
return bedrockDestinationContainer == slotType(container) && javaDestinationSlot >= this.size;
}
@Override
public boolean requiresOpeningDelay(GeyserSession session, Inventory inventory) {
return inventory instanceof Container container && !container.isUsingRealBlock();
public boolean requiresOpeningDelay(GeyserSession session, Type container) {
return !container.isUsingRealBlock();
}
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
updater.updateInventory(this, session, inventory);
public void updateInventory(GeyserSession session, Type container) {
updater.updateInventory(this, session, container);
}
@Override
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
updater.updateSlot(this, session, inventory, slot);
public void updateSlot(GeyserSession session, Type container, int slot) {
updater.updateSlot(this, session, container, slot);
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot, Type inventory) {
if (javaSlot < this.size) {
return new BedrockContainerSlot(ContainerSlotType.LEVEL_ENTITY, javaSlot);
return new BedrockContainerSlot(slotType(inventory), javaSlot);
}
return super.javaSlotToBedrockContainer(javaSlot);
return super.javaSlotToBedrockContainer(javaSlot, inventory);
}
/**
* Overridden by the SingleChestInventoryTranslator in case barrels are used.
* Bedrock uses the ContainerSlotType.BARREL for those.
*/
protected ContainerSlotType slotType(Type type) {
return ContainerSlotType.LEVEL_ENTITY;
}
}

View file

@ -52,7 +52,7 @@ import org.geysermc.geyser.util.InventoryUtils;
import java.util.Objects;
public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
public class DoubleChestInventoryTranslator extends ChestInventoryTranslator<Container> {
private final int defaultJavaBlockState;
public DoubleChestInventoryTranslator(int size) {
@ -68,9 +68,9 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
* Mirrors {@link BlockInventoryHolder#canReuseContainer(GeyserSession, Container, Container)}
*/
@Override
public boolean canReuseInventory(GeyserSession session, @NonNull Inventory inventory, @NonNull Inventory oldInventory) {
if (!super.canReuseInventory(session, inventory, oldInventory) ||
!(inventory instanceof Container container) ||
public boolean canReuseInventory(GeyserSession session, @NonNull Inventory newInventory, @NonNull Inventory oldInventory) {
if (!super.canReuseInventory(session, newInventory, oldInventory) ||
!(newInventory instanceof Container) ||
!(oldInventory instanceof Container previous)
) {
return false;
@ -86,15 +86,15 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
if (Objects.equals(position, previous.getHolderPosition())) {
return true;
} else {
GeyserImpl.getInstance().getLogger().debug(session, "Not reusing inventory (%s) due to virtual block holder changing (%s -> %s)!",
InventoryUtils.debugInventory(inventory), previous.getHolderPosition(), position);
GeyserImpl.getInstance().getLogger().debug(session, "Not reusing inventory due to virtual block holder changing (%s -> %s)!",
previous.getHolderPosition(), position);
return false;
}
}
@Override
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
if (canUseRealBlock(session, inventory)) {
public boolean prepareInventory(GeyserSession session, Container container) {
if (canUseRealBlock(session, container)) {
return true;
}
@ -116,7 +116,7 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
NbtMapBuilder tag = BlockEntityTranslator.getConstantBedrockTag("Chest", position)
.putInt("pairx", pairPosition.getX())
.putInt("pairz", pairPosition.getZ())
.putString("CustomName", inventory.getTitle())
.putString("CustomName", container.getTitle())
.putBoolean("pairlead", false);
BlockEntityDataPacket dataPacket = new BlockEntityDataPacket();
@ -138,7 +138,7 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
.putInt("z", pairPosition.getZ())
.putInt("pairx", position.getX())
.putInt("pairz", position.getZ())
.putString("CustomName", inventory.getTitle())
.putString("CustomName", container.getTitle())
.putBoolean("pairlead", true);
dataPacket = new BlockEntityDataPacket();
@ -146,53 +146,37 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
dataPacket.setBlockPosition(pairPosition);
session.sendUpstreamPacket(dataPacket);
inventory.setHolderPosition(position);
container.setHolderPosition(position);
return true;
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
public void openInventory(GeyserSession session, Container container) {
ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket();
containerOpenPacket.setId((byte) inventory.getBedrockId());
containerOpenPacket.setId((byte) container.getBedrockId());
containerOpenPacket.setType(ContainerType.CONTAINER);
containerOpenPacket.setBlockPosition(inventory.getHolderPosition());
containerOpenPacket.setUniqueEntityId(inventory.getHolderId());
containerOpenPacket.setBlockPosition(container.getHolderPosition());
containerOpenPacket.setUniqueEntityId(container.getHolderId());
session.sendUpstreamPacket(containerOpenPacket);
GeyserImpl.getInstance().getLogger().debug(session, containerOpenPacket.toString());
}
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
// this should no longer be possible; as we're storing the translator with the inventory to avoid desyncs.
// TODO use generics to ensure we don't need to cast unsafely in the first place
if (!(inventory instanceof Container container)) {
GeyserImpl.getInstance().getLogger().warning("Tried to close a non-container inventory in a block inventory holder! Please report this error on discord.");
GeyserImpl.getInstance().getLogger().warning("Current inventory translator: " + InventoryUtils.getInventoryTranslator(session).getClass().getSimpleName());
GeyserImpl.getInstance().getLogger().warning("Current inventory: " + inventory.getClass().getSimpleName());
// Try to save ourselves? maybe?
// https://github.com/GeyserMC/Geyser/issues/4141
// TODO: improve once this issue is pinned down
if (session.getOpenInventory() != null) {
session.getOpenInventory().getTranslator().closeInventory(session, inventory);
session.setOpenInventory(null);
}
return;
}
public void closeInventory(GeyserSession session, Container container, boolean force) {
// No need to reset a block since we didn't change any blocks
// But send a container close packet because we aren't destroying the original.
if (container.isDisplayed()) {
ContainerClosePacket packet = new ContainerClosePacket();
packet.setId((byte) inventory.getBedrockId());
packet.setId((byte) container.getBedrockId());
packet.setServerInitiated(true);
packet.setType(ContainerType.CONTAINER);
session.sendUpstreamPacket(packet);
}
if (!container.isUsingRealBlock()) {
Vector3i holderPos = inventory.getHolderPosition();
Vector3i holderPos = container.getHolderPosition();
int realBlock = session.getGeyser().getWorldManager().getBlockAt(session, holderPos);
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
blockPacket.setDataLayer(0);
@ -210,18 +194,18 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
}
}
private boolean canUseRealBlock(GeyserSession session, Inventory inventory) {
private boolean canUseRealBlock(GeyserSession session, Container container) {
// See BlockInventoryHolder - same concept there except we're also dealing with a specific block state
if (session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition())) {
BlockState state = session.getGeyser().getWorldManager().blockAt(session, session.getLastInteractionBlockPosition());
if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(state.javaId())) {
if ((state.block() == Blocks.CHEST || state.block() == Blocks.TRAPPED_CHEST)
&& state.getValue(Properties.CHEST_TYPE) != ChestType.SINGLE) {
inventory.setHolderPosition(session.getLastInteractionBlockPosition());
((Container) inventory).setUsingRealBlock(true, state.block());
container.setHolderPosition(session.getLastInteractionBlockPosition());
container.setUsingRealBlock(true, state.block());
NbtMapBuilder tag = BlockEntityTranslator.getConstantBedrockTag("Chest", session.getLastInteractionBlockPosition())
.putString("CustomName", inventory.getTitle());
.putString("CustomName", container.getTitle());
DoubleChestBlockEntityTranslator.translateChestValue(tag, state,
session.getLastInteractionBlockPosition().getX(), session.getLastInteractionBlockPosition().getZ());

View file

@ -25,9 +25,9 @@
package org.geysermc.geyser.translator.inventory.chest;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.Generic9X3Container;
import org.geysermc.geyser.inventory.holder.BlockInventoryHolder;
import org.geysermc.geyser.inventory.holder.InventoryHolder;
import org.geysermc.geyser.level.block.Blocks;
@ -36,7 +36,7 @@ import org.geysermc.geyser.level.block.property.Properties;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.session.GeyserSession;
public class SingleChestInventoryTranslator extends ChestInventoryTranslator {
public class SingleChestInventoryTranslator extends ChestInventoryTranslator<Generic9X3Container> {
private final InventoryHolder holder;
public SingleChestInventoryTranslator(int size) {
@ -57,17 +57,30 @@ public class SingleChestInventoryTranslator extends ChestInventoryTranslator {
}
@Override
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
return holder.prepareInventory(session, (Container) inventory);
public boolean prepareInventory(GeyserSession session, Generic9X3Container container) {
return holder.prepareInventory(session, container);
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
holder.openInventory(session, (Container) inventory);
public void openInventory(GeyserSession session, Generic9X3Container container) {
holder.openInventory(session, container);
}
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
holder.closeInventory(session, (Container) inventory, ContainerType.CONTAINER);
public void closeInventory(GeyserSession session, Generic9X3Container container, boolean force) {
holder.closeInventory(session, container, ContainerType.CONTAINER);
}
@Override
public Generic9X3Container createInventory(GeyserSession session, String name, int windowId, org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType containerType) {
return new Generic9X3Container(session, name, windowId, this.size, containerType);
}
@Override
protected ContainerSlotType slotType(Generic9X3Container generic9X3Container) {
if (generic9X3Container.isBarrel()) {
return ContainerSlotType.BARREL;
}
return super.slotType(generic9X3Container);
}
}

View file

@ -29,7 +29,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.cloudburstmc.protocol.bedrock.packet.ContainerSetDataPacket;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.SlotType;
import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater;
import org.geysermc.geyser.level.block.property.Properties;
@ -37,15 +37,15 @@ import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.AbstractBlockInventoryTranslator;
public abstract class AbstractFurnaceInventoryTranslator extends AbstractBlockInventoryTranslator {
public abstract class AbstractFurnaceInventoryTranslator extends AbstractBlockInventoryTranslator<Container> {
AbstractFurnaceInventoryTranslator(Block javaBlock, ContainerType containerType) {
super(3, javaBlock.defaultBlockState().withValue(Properties.LIT, false), containerType, ContainerInventoryUpdater.INSTANCE);
}
@Override
public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) {
public void updateProperty(GeyserSession session, Container container, int key, int value) {
ContainerSetDataPacket dataPacket = new ContainerSetDataPacket();
dataPacket.setWindowId((byte) inventory.getBedrockId());
dataPacket.setWindowId((byte) container.getBedrockId());
switch (key) {
case 0:
dataPacket.setProperty(ContainerSetDataPacket.FURNACE_LIT_TIME);
@ -71,13 +71,13 @@ public abstract class AbstractFurnaceInventoryTranslator extends AbstractBlockIn
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, Container container) {
if (slot == 1) {
return new BedrockContainerSlot(ContainerSlotType.FURNACE_FUEL, javaSlotToBedrock(slot));
}
if (slot == 2) {
return new BedrockContainerSlot(ContainerSlotType.FURNACE_RESULT, javaSlotToBedrock(slot));
}
return super.javaSlotToBedrockContainer(slot);
return super.javaSlotToBedrockContainer(slot, container);
}
}

View file

@ -29,7 +29,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.level.block.Blocks;
public class BlastFurnaceInventoryTranslator extends AbstractFurnaceInventoryTranslator {
@ -38,15 +38,15 @@ public class BlastFurnaceInventoryTranslator extends AbstractFurnaceInventoryTra
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, Container container) {
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.BLAST_FURNACE_INGREDIENT, javaSlotToBedrock(slot));
}
return super.javaSlotToBedrockContainer(slot);
return super.javaSlotToBedrockContainer(slot, container);
}
@Override
public @Nullable ContainerType closeContainerType(Inventory inventory) {
public @Nullable ContainerType closeContainerType(Container container) {
return ContainerType.BLAST_FURNACE;
}
}

View file

@ -29,7 +29,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.level.block.Blocks;
public class FurnaceInventoryTranslator extends AbstractFurnaceInventoryTranslator {
@ -38,15 +38,15 @@ public class FurnaceInventoryTranslator extends AbstractFurnaceInventoryTranslat
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, Container container) {
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.FURNACE_INGREDIENT, javaSlotToBedrock(slot));
}
return super.javaSlotToBedrockContainer(slot);
return super.javaSlotToBedrockContainer(slot, container);
}
@Override
public @Nullable ContainerType closeContainerType(Inventory inventory) {
public @Nullable ContainerType closeContainerType(Container container) {
return ContainerType.FURNACE;
}
}

View file

@ -29,7 +29,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.level.block.Blocks;
public class SmokerInventoryTranslator extends AbstractFurnaceInventoryTranslator {
@ -38,15 +38,15 @@ public class SmokerInventoryTranslator extends AbstractFurnaceInventoryTranslato
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, Container container) {
if (slot == 0) {
return new BedrockContainerSlot(ContainerSlotType.SMOKER_INGREDIENT, javaSlotToBedrock(slot));
}
return super.javaSlotToBedrockContainer(slot);
return super.javaSlotToBedrockContainer(slot, container);
}
@Override
public @Nullable ContainerType closeContainerType(Inventory inventory) {
public @Nullable ContainerType closeContainerType(Container container) {
return ContainerType.SMOKER;
}
}

View file

@ -25,13 +25,13 @@
package org.geysermc.geyser.translator.inventory.horse;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.updater.HorseInventoryUpdater;
import org.geysermc.geyser.inventory.updater.InventoryUpdater;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.BaseInventoryTranslator;
public abstract class AbstractHorseInventoryTranslator extends BaseInventoryTranslator {
public abstract class AbstractHorseInventoryTranslator extends BaseInventoryTranslator<Container> {
private final InventoryUpdater updater;
public AbstractHorseInventoryTranslator(int size) {
@ -40,27 +40,27 @@ public abstract class AbstractHorseInventoryTranslator extends BaseInventoryTran
}
@Override
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
public boolean prepareInventory(GeyserSession session, Container container) {
return true;
}
@Override
public void openInventory(GeyserSession session, Inventory inventory) {
public void openInventory(GeyserSession session, Container container) {
}
@Override
public void closeInventory(GeyserSession session, Inventory inventory) {
public void closeInventory(GeyserSession session, Container container, boolean force) {
// TODO find a way to implement
// Can cause inventory de-sync if the Java server requests an inventory close
}
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
updater.updateInventory(this, session, inventory);
public void updateInventory(GeyserSession session, Container container) {
updater.updateInventory(this, session, container);
}
@Override
public void updateSlot(GeyserSession session, Inventory inventory, int slot) {
updater.updateSlot(this, session, inventory, slot);
public void updateSlot(GeyserSession session, Container container, int slot) {
updater.updateSlot(this, session, container, slot);
}
}

View file

@ -31,7 +31,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData;
import org.cloudburstmc.protocol.bedrock.packet.InventoryContentPacket;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.session.GeyserSession;
import java.util.Arrays;
@ -63,14 +63,14 @@ public abstract class ChestedHorseInventoryTranslator extends AbstractHorseInven
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, Container container) {
if (slot == this.equipSlot) {
return new BedrockContainerSlot(ContainerSlotType.HORSE_EQUIP, 0);
}
if (slot <= this.size - 1) { // Accommodate for the lack of one slot (saddle or armor)
return new BedrockContainerSlot(ContainerSlotType.LEVEL_ENTITY, slot - 1);
}
return super.javaSlotToBedrockContainer(slot);
return super.javaSlotToBedrockContainer(slot, container);
}
@Override
@ -85,11 +85,11 @@ public abstract class ChestedHorseInventoryTranslator extends AbstractHorseInven
}
@Override
public void updateInventory(GeyserSession session, Inventory inventory) {
public void updateInventory(GeyserSession session, Container container) {
ItemData[] bedrockItems = new ItemData[36];
for (int i = 0; i < 36; i++) {
final int offset = i < 9 ? 27 : -9;
bedrockItems[i] = inventory.getItem(this.size + i + offset).getItemData(session);
bedrockItems[i] = container.getItem(this.size + i + offset).getItemData(session);
}
InventoryContentPacket contentPacket = new InventoryContentPacket();
contentPacket.setContainerId(ContainerId.INVENTORY);
@ -99,13 +99,13 @@ public abstract class ChestedHorseInventoryTranslator extends AbstractHorseInven
ItemData[] horseItems = new ItemData[chestSize + 1];
// Manually specify the first slot - Java always has two slots (armor and saddle) and one is invisible.
// Bedrock doesn't have this invisible slot.
horseItems[0] = inventory.getItem(this.equipSlot).getItemData(session);
horseItems[0] = container.getItem(this.equipSlot).getItemData(session);
for (int i = 1; i < horseItems.length; i++) {
horseItems[i] = inventory.getItem(i + 1).getItemData(session);
horseItems[i] = container.getItem(i + 1).getItemData(session);
}
InventoryContentPacket horseContentsPacket = new InventoryContentPacket();
horseContentsPacket.setContainerId(inventory.getBedrockId());
horseContentsPacket.setContainerId(container.getBedrockId());
horseContentsPacket.setContents(Arrays.asList(horseItems));
session.sendUpstreamPacket(horseContentsPacket);
}

View file

@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.inventory.horse;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData;
import org.geysermc.geyser.inventory.BedrockContainerSlot;
import org.geysermc.geyser.inventory.Container;
public class HorseInventoryTranslator extends AbstractHorseInventoryTranslator {
public HorseInventoryTranslator(int size) {
@ -43,10 +44,10 @@ public class HorseInventoryTranslator extends AbstractHorseInventoryTranslator {
}
@Override
public BedrockContainerSlot javaSlotToBedrockContainer(int slot) {
public BedrockContainerSlot javaSlotToBedrockContainer(int slot, Container container) {
if (slot == 0 || slot == 1) {
return new BedrockContainerSlot(ContainerSlotType.HORSE_EQUIP, slot);
}
return super.javaSlotToBedrockContainer(slot);
return super.javaSlotToBedrockContainer(slot, container);
}
}

View file

@ -132,7 +132,7 @@ public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket>
// Update local copy
session.getPlayerInventory().setItem(36 + session.getPlayerInventory().getHeldItemSlot(), GeyserItemStack.from(bookItem), session);
session.getPlayerInventory().updateInventory();
session.getPlayerInventoryHolder().updateInventory();
String title;
if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) {

View file

@ -29,6 +29,7 @@ import org.cloudburstmc.protocol.bedrock.packet.ContainerClosePacket;
import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.geyser.inventory.MerchantContainer;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.MerchantInventoryTranslator;
@ -54,17 +55,18 @@ public class BedrockContainerCloseTranslator extends PacketTranslator<ContainerC
session.setClosingInventory(false);
// 1.21.70: Bedrock can reject opening inventories - in those cases it replies with -1
Inventory openInventory = session.getOpenInventory();
if (bedrockId == -1 && openInventory != null) {
InventoryHolder<? extends Inventory> holder = session.getInventoryHolder();
if (bedrockId == -1 && holder != null) {
// 1.16.200 - window ID is always -1 sent from Bedrock for merchant containers
if (openInventory.getTranslator() instanceof MerchantInventoryTranslator) {
bedrockId = (byte) openInventory.getBedrockId();
} else if (openInventory.getBedrockId() == session.getPendingOrCurrentBedrockInventoryId()) {
if (holder.translator() instanceof MerchantInventoryTranslator) {
bedrockId = (byte) holder.bedrockId();
} else if (holder.bedrockId() == session.getPendingOrCurrentBedrockInventoryId()) {
// If virtual inventories are opened too quickly, they can be occasionally rejected
// We just try and queue a new one.
// Before making another attempt to re-open, let's make sure we actually need this inventory open.
if (session.getContainerOpenAttempts() < 3) {
openInventory.setPending(true);
if (holder.containerOpenAttempts() < 5) {
holder.incrementContainerOpenAttempts();
holder.pending(true);
session.scheduleInEventLoop(() -> {
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
@ -75,26 +77,25 @@ public class BedrockContainerCloseTranslator extends PacketTranslator<ContainerC
}, 100, TimeUnit.MILLISECONDS);
return;
} else {
GeyserImpl.getInstance().getLogger().debug(session, "Exceeded 3 attempts to open a virtual inventory!");
GeyserImpl.getInstance().getLogger().debug(session, packet + " " + session.getOpenInventory().getClass().getSimpleName());
GeyserImpl.getInstance().getLogger().debug(session, "Exceeded 5 attempts to open a virtual inventory!");
GeyserImpl.getInstance().getLogger().debug(session, packet + " " + session.getInventoryHolder().getClass().getSimpleName());
}
}
}
session.setPendingOrCurrentBedrockInventoryId(-1);
session.setContainerOpenAttempts(0);
closeCurrentOrOpenPending(session, bedrockId, openInventory);
closeCurrentOrOpenPending(session, bedrockId, holder);
}
private void closeCurrentOrOpenPending(GeyserSession session, byte bedrockId, Inventory openInventory) {
if (openInventory != null) {
if (bedrockId == openInventory.getBedrockId()) {
InventoryUtils.sendJavaContainerClose(session, openInventory);
InventoryUtils.closeInventory(session, openInventory.getJavaId(), false);
} else if (openInventory.isPending()) {
InventoryUtils.displayInventory(session, openInventory);
private void closeCurrentOrOpenPending(GeyserSession session, byte bedrockId, InventoryHolder<? extends Inventory> holder) {
if (holder != null) {
if (bedrockId == holder.bedrockId()) {
InventoryUtils.sendJavaContainerClose(holder);
InventoryUtils.closeInventory(session, holder, false);
} else if (holder.pending()) {
InventoryUtils.displayInventory(holder);
if (openInventory instanceof MerchantContainer merchantContainer && merchantContainer.getPendingOffersPacket() != null) {
if (holder.inventory() instanceof MerchantContainer merchantContainer && merchantContainer.getPendingOffersPacket() != null) {
JavaMerchantOffersTranslator.openMerchant(session, merchantContainer.getPendingOffersPacket(), merchantContainer);
}
}

View file

@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.bedrock;
import org.cloudburstmc.protocol.bedrock.packet.FilterTextPacket;
import org.geysermc.geyser.inventory.AnvilContainer;
import org.geysermc.geyser.inventory.CartographyContainer;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@ -41,15 +42,20 @@ public class BedrockFilterTextTranslator extends PacketTranslator<FilterTextPack
@Override
public void translate(GeyserSession session, FilterTextPacket packet) {
if (session.getOpenInventory() instanceof CartographyContainer) {
// We don't want to be able to rename in the cartography table
return;
InventoryHolder<?> holder = session.getInventoryHolder();
if (holder != null) {
if (holder.inventory() instanceof CartographyContainer) {
// We don't want to be able to rename in the cartography table
return;
}
if (holder.inventory() instanceof AnvilContainer anvilContainer) {
packet.setText(anvilContainer.checkForRename(session, packet.getText()));
holder.updateSlot(1);
}
}
packet.setFromServer(true);
if (session.getOpenInventory() instanceof AnvilContainer anvilContainer) {
packet.setText(anvilContainer.checkForRename(session, packet.getText()));
anvilContainer.updateSlot(1);
}
session.sendUpstreamPacket(packet);
}
}

View file

@ -70,7 +70,6 @@ import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.cache.SkullCache;
import org.geysermc.geyser.skin.FakeHeadProvider;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@ -363,7 +362,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
}
case 1 -> {
if (isIncorrectHeldItem(session, packet)) {
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, session.getPlayerInventory(), session.getPlayerInventory().getOffsetForHotbar(packet.getHotbarSlot()));
session.getPlayerInventoryHolder().updateSlot(session.getPlayerInventory().getOffsetForHotbar(packet.getHotbarSlot()));
break;
}
@ -580,7 +579,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
session.sendUpstreamPacket(updateWaterPacket);
// Reset the item in hand to prevent "missing" blocks
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, session.getPlayerInventory(), session.getPlayerInventory().getHeldItemSlot()); // TODO test
session.getPlayerInventoryHolder().updateSlot(session.getPlayerInventory().getHeldItemSlot()); // TODO test
}
private boolean isIncorrectHeldItem(GeyserSession session, InventoryTransactionPacket packet) {
@ -599,9 +598,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
private boolean useItem(GeyserSession session, InventoryTransactionPacket packet, int blockState) {
// Update the player's inventory to remove any items added by the client itself
Inventory playerInventory = session.getPlayerInventory();
PlayerInventory playerInventory = session.getPlayerInventory();
int heldItemSlot = playerInventory.getOffsetForHotbar(packet.getHotbarSlot());
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, playerInventory, heldItemSlot);
session.getPlayerInventoryHolder().updateSlot(heldItemSlot);
GeyserItemStack itemStack = playerInventory.getItem(heldItemSlot);
if (itemStack.getAmount() > 1) {
if (itemStack.asItem() == Items.BUCKET || itemStack.asItem() == Items.GLASS_BOTTLE) {
@ -614,7 +613,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
slot = playerInventory.getOffsetForHotbar(slot);
}
if (playerInventory.getItem(slot).isEmpty()) {
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, playerInventory, slot);
session.getPlayerInventoryHolder().updateSlot(slot);
break;
}
}

View file

@ -26,12 +26,10 @@
package org.geysermc.geyser.translator.protocol.bedrock;
import org.cloudburstmc.protocol.bedrock.packet.ItemStackRequestPacket;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.InventoryHolder;
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;
/**
* The packet sent for server-authoritative-style inventory transactions.
@ -41,11 +39,10 @@ public class BedrockItemStackRequestTranslator extends PacketTranslator<ItemStac
@Override
public void translate(GeyserSession session, ItemStackRequestPacket packet) {
Inventory inventory = session.getOpenInventory();
if (inventory == null)
InventoryHolder<?> holder = session.getInventoryHolder();
if (holder == null)
return;
InventoryTranslator translator = InventoryUtils.getInventoryTranslator(session);
translator.translateRequests(session, inventory, packet.getRequests());
holder.translateRequests(packet.getRequests());
}
}

View file

@ -26,7 +26,7 @@
package org.geysermc.geyser.translator.protocol.bedrock;
import org.cloudburstmc.protocol.bedrock.packet.LecternUpdatePacket;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.geyser.inventory.LecternContainer;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
@ -43,15 +43,16 @@ public class BedrockLecternUpdateTranslator extends PacketTranslator<LecternUpda
@Override
public void translate(GeyserSession session, LecternUpdatePacket packet) {
// Bedrock wants to either move a page or exit
if (!(session.getOpenInventory() instanceof LecternContainer lecternContainer)) {
InventoryHolder<?> holder = session.getInventoryHolder();
if (holder == null || !(holder.inventory() instanceof LecternContainer lecternContainer)) {
session.getGeyser().getLogger().debug("Expected lectern but it wasn't open!");
return;
}
if (lecternContainer.getCurrentBedrockPage() == packet.getPage()) {
// The same page means Bedrock is closing the window
InventoryUtils.sendJavaContainerClose(session, lecternContainer);
InventoryUtils.closeInventory(session, lecternContainer.getJavaId(), false);
InventoryUtils.sendJavaContainerClose(holder);
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)
@ -61,8 +62,7 @@ public class BedrockLecternUpdateTranslator extends PacketTranslator<LecternUpda
// So, fun fact: We need to separately handle fake lecterns!
// Since those are not actually a real lectern... the Java server won't respond to our requests.
if (!lecternContainer.isUsingRealBlock()) {
Inventory inventory = session.getOpenInventory();
inventory.getTranslator().updateProperty(session, inventory, 0, newJavaPage);
holder.updateProperty(0, newJavaPage);
return;
}

View file

@ -65,7 +65,7 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat
session.getEntityCache().updateBossBars();
// Double sigh - https://github.com/GeyserMC/Geyser/issues/2677 - as of Bedrock 1.18
if (session.getOpenInventory() != null) {
if (session.getInventoryHolder() != null) {
InventoryUtils.openPendingInventory(session);
}

View file

@ -25,13 +25,12 @@
package org.geysermc.geyser.translator.protocol.bedrock.entity;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundSelectTradePacket;
import org.cloudburstmc.protocol.bedrock.packet.EntityEventPacket;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.MerchantContainer;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundSelectTradePacket;
import java.util.concurrent.TimeUnit;
@ -52,8 +51,7 @@ public class BedrockEntityEventTranslator extends PacketTranslator<EntityEventPa
session.sendDownstreamGamePacket(selectTradePacket);
session.scheduleInEventLoop(() -> {
Inventory openInventory = session.getOpenInventory();
if (openInventory instanceof MerchantContainer merchantInventory) {
if (session.getOpenInventory() instanceof MerchantContainer merchantInventory) {
merchantInventory.onTradeSelected(session, packet.getData());
}
}, 100, TimeUnit.MILLISECONDS);

View file

@ -121,14 +121,14 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
}
break;
case OPEN_INVENTORY:
if (session.getOpenInventory() == null) {
if (session.getInventoryHolder() == null) {
Entity ridingEntity = session.getPlayerEntity().getVehicle();
if (ridingEntity instanceof AbstractHorseEntity || ridingEntity instanceof ChestBoatEntity) {
// This mob has an inventory of its own that we should open instead.
ServerboundPlayerCommandPacket openVehicleWindowPacket = new ServerboundPlayerCommandPacket(session.getPlayerEntity().getEntityId(), PlayerState.OPEN_VEHICLE_INVENTORY);
session.sendDownstreamGamePacket(openVehicleWindowPacket);
} else {
InventoryUtils.openInventory(session, session.getPlayerInventory());
InventoryUtils.openInventory(session.getPlayerInventoryHolder());
}
}
}

View file

@ -44,7 +44,7 @@ public class BedrockSetPlayerInventoryOptionsTranslator extends PacketTranslator
// This should ensure that we never send these packets when the player inventory is opened while in creative
// Java edition can't craft in the 2x2 grid in creative, and subsequently doesn't have a recipe book
if (session.getGameMode() == GameMode.CREATIVE && session.getPlayerInventory() == session.getOpenInventory()) {
if (session.getGameMode() == GameMode.CREATIVE && session.getPlayerInventoryHolder() == session.getInventoryHolder()) {
return;
}

View file

@ -61,7 +61,7 @@ public class JavaRespawnTranslator extends PacketTranslator<ClientboundRespawnPa
entity.setHealth(entity.getMaxHealth());
entity.getAttributes().put(GeyserAttributeType.HEALTH, entity.createHealthAttribute());
session.setOpenInventory(null);
session.setInventoryHolder(null);
session.setClosingInventory(false);
entity.setLastDeathPosition(spawnInfo.getLastDeathPos());

View file

@ -48,7 +48,6 @@ import org.geysermc.geyser.entity.type.living.monster.CreakingEntity;
import org.geysermc.geyser.entity.type.living.monster.WardenEntity;
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;
@ -186,7 +185,7 @@ public class JavaEntityEventTranslator extends PacketTranslator<ClientboundEntit
if (totemItemWorkaround) {
// Reset the item again
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, session.getPlayerInventory(), 45);
session.getPlayerInventoryHolder().updateSlot(45);
}
return;

View file

@ -25,7 +25,9 @@
package org.geysermc.geyser.translator.protocol.java.inventory;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.geyser.inventory.LecternContainer;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@ -39,7 +41,19 @@ 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
session.setServerRequestedClosePlayerInventory(packet.getContainerId() == 0 && session.getOpenInventory() instanceof PlayerInventory);
InventoryUtils.closeInventory(session, packet.getContainerId(), (session.getOpenInventory() != null && session.getOpenInventory().getJavaId() == packet.getContainerId()));
InventoryHolder<? extends Inventory> holder = session.getInventoryHolder();
boolean confirm = false;
if (holder != null) {
if (packet.getContainerId() == 0) {
if (holder.inventory() instanceof LecternContainer container && container.isBookInPlayerInventory()) {
InventoryUtils.closeInventory(session, holder, true);
return;
}
}
confirm = holder.javaId() == packet.getContainerId();
}
InventoryUtils.closeInventory(session, packet.getContainerId(), confirm);
}
}

View file

@ -28,9 +28,8 @@ package org.geysermc.geyser.translator.protocol.java.inventory;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator;
import org.geysermc.geyser.translator.inventory.SmithingInventoryTranslator;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@ -42,10 +41,12 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
@Override
public void translate(GeyserSession session, ClientboundContainerSetContentPacket packet) {
Inventory inventory = InventoryUtils.getInventory(session, packet.getContainerId());
if (inventory == null)
InventoryHolder<?> holder = InventoryUtils.getInventory(session, packet.getContainerId());
if (holder == null)
return;
Inventory inventory = holder.inventory();
int inventorySize = inventory.getSize();
for (int i = 0; i < packet.getItems().length; i++) {
if (i >= inventorySize) {
@ -56,7 +57,7 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
logger.debug(packet);
logger.debug(inventory);
}
updateInventory(session, inventory, packet.getContainerId());
holder.updateInventory();
// 1.18.1 behavior: the previous items will be correctly set, but the state ID and carried item will not
// as this produces a stack trace on the client.
// If Java processes this correctly in the future, we can revert this behavior
@ -68,7 +69,7 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
inventory.setItem(i, newItem, session);
}
updateInventory(session, inventory, packet.getContainerId());
holder.updateInventory();
int stateId = packet.getStateId();
session.setEmulatePost1_16Logic(stateId > 0 || stateId != inventory.getStateId());
@ -79,25 +80,15 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
session.getPlayerInventory().setCursor(cursor, session);
InventoryUtils.updateCursor(session);
if (InventoryUtils.getInventoryTranslator(session) instanceof SmithingInventoryTranslator) {
if (holder.translator() instanceof SmithingInventoryTranslator) {
// On 1.21.1, the recipe output is sometimes only updated here.
// This can be replicated with shift-clicking the last item into the smithing table.
// It seems that something in Via 5.1.1 causes 1.21.3 clients - even Java ones -
// to make the server send a slot update.
// That plus shift-clicking means that the state ID becomes outdated and forces
// a complete inventory update.
JavaContainerSetSlotTranslator.updateSmithingTableOutput(session, SmithingInventoryTranslator.OUTPUT,
packet.getItems()[SmithingInventoryTranslator.OUTPUT], inventory);
}
}
private void updateInventory(GeyserSession session, Inventory inventory, int containerId) {
InventoryTranslator translator = InventoryUtils.getInventoryTranslator(session);
if (containerId == 0 && !(translator instanceof PlayerInventoryTranslator)) {
// In rare cases, the window ID can still be 0 but Java treats it as valid
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateInventory(session, inventory);
} else {
translator.updateInventory(session, inventory);
JavaContainerSetSlotTranslator.updateSmithingTableOutput(SmithingInventoryTranslator.OUTPUT,
packet.getItems()[SmithingInventoryTranslator.OUTPUT], holder);
}
}
}

View file

@ -25,7 +25,7 @@
package org.geysermc.geyser.translator.protocol.java.inventory;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@ -37,7 +37,7 @@ public class JavaContainerSetDataTranslator extends PacketTranslator<Clientbound
@Override
public void translate(GeyserSession session, ClientboundContainerSetDataPacket packet) {
Inventory inventory = InventoryUtils.getInventory(session, packet.getContainerId());
InventoryHolder<?> inventory = InventoryUtils.getInventory(session, packet.getContainerId());
if (inventory == null)
return;

View file

@ -36,12 +36,11 @@ import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserSmithingRecipe;
import org.geysermc.geyser.item.Items;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.inventory.PlayerInventoryTranslator;
import org.geysermc.geyser.translator.inventory.SmithingInventoryTranslator;
import org.geysermc.geyser.translator.item.ItemTranslator;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
@ -64,68 +63,60 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
@Override
public void translate(GeyserSession session, ClientboundContainerSetSlotPacket packet) {
//TODO: support window id -2, should update player inventory
//TODO: ^ I think this is outdated.
Inventory inventory = InventoryUtils.getInventory(session, packet.getContainerId());
if (inventory == null) {
InventoryHolder<?> holder = InventoryUtils.getInventory(session, packet.getContainerId());
if (holder == null) {
return;
}
InventoryTranslator translator = InventoryUtils.getInventoryTranslator(session);
if (translator != null) {
int slot = packet.getSlot();
if (slot >= inventory.getSize()) {
GeyserLogger logger = session.getGeyser().getLogger();
logger.warning("ClientboundContainerSetSlotPacket sent to " + session.bedrockUsername()
+ " that exceeds inventory size!");
if (logger.isDebug()) {
logger.debug(packet.toString());
logger.debug(inventory.toString());
}
// 1.19.0 behavior: the state ID will not be set due to exception
return;
Inventory inventory = holder.inventory();
int slot = packet.getSlot();
if (slot >= inventory.getSize()) {
GeyserLogger logger = session.getGeyser().getLogger();
logger.warning("ClientboundContainerSetSlotPacket sent to " + session.bedrockUsername()
+ " that exceeds inventory size!");
if (logger.isDebug()) {
logger.debug(packet.toString());
logger.debug(inventory.toString());
}
if (translator instanceof SmithingInventoryTranslator) {
updateSmithingTableOutput(session, slot, packet.getItem(), inventory);
} else {
updateCraftingGrid(session, slot, packet.getItem(), inventory, translator);
}
GeyserItemStack newItem = GeyserItemStack.from(packet.getItem());
session.getBundleCache().initialize(newItem);
if (packet.getContainerId() == 0 && !(translator instanceof PlayerInventoryTranslator)) {
// In rare cases, the window ID can still be 0 but Java treats it as valid
// This behavior still exists as of Java Edition 1.21.2, despite the new packet
session.getPlayerInventory().setItem(slot, newItem, session);
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, session.getPlayerInventory(), slot);
} else {
inventory.setItem(slot, newItem, session);
translator.updateSlot(session, inventory, slot);
}
// Intentional behavior here below the cursor; Minecraft 1.18.1 also does this.
int stateId = packet.getStateId();
session.setEmulatePost1_16Logic(stateId > 0 || stateId != inventory.getStateId());
inventory.setStateId(stateId);
// 1.19.0 behavior: the state ID will not be set due to exception
return;
}
if (holder.translator() instanceof SmithingInventoryTranslator) {
updateSmithingTableOutput(slot, packet.getItem(), holder);
} else {
updateCraftingGrid(slot, packet.getItem(), holder);
}
GeyserItemStack newItem = GeyserItemStack.from(packet.getItem());
session.getBundleCache().initialize(newItem);
holder.inventory().setItem(slot, newItem, session);
holder.updateSlot(slot);
// Intentional behavior here below the cursor; Minecraft 1.18.1 also does this.
int stateId = packet.getStateId();
session.setEmulatePost1_16Logic(stateId > 0 || stateId != inventory.getStateId());
inventory.setStateId(stateId);
}
/**
* Checks for a changed output slot in the crafting grid, and ensures Bedrock sees the recipe.
*/
private static void updateCraftingGrid(GeyserSession session, int slot, ItemStack item, Inventory inventory, InventoryTranslator translator) {
private static void updateCraftingGrid(int slot, ItemStack item, InventoryHolder<? extends Inventory> holder) {
// Check if it's the crafting grid result slot.
if (slot != 0) {
return;
}
// Check if there is any crafting grid.
int gridSize = translator.getGridSize();
int gridSize = holder.translator().getGridSize();
if (gridSize == -1) {
return;
}
GeyserSession session = holder.session();
// Only process the most recent crafting grid result, and cancel the previous one.
if (session.getContainerOutputFuture() != null) {
session.getContainerOutputFuture().cancel(false);
@ -142,7 +133,7 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
int firstCol = -1, width = -1;
for (int row = 0; row < gridDimensions; row++) {
for (int col = 0; col < gridDimensions; col++) {
if (!inventory.getItem(col + (row * gridDimensions) + 1).isEmpty()) {
if (!holder.inventory().getItem(col + (row * gridDimensions) + 1).isEmpty()) {
if (firstRow == -1) {
firstRow = row;
firstCol = col;
@ -163,7 +154,7 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
height += -firstRow + 1;
width += -firstCol + 1;
if (InventoryUtils.getValidRecipe(session, item, inventory::getItem, gridDimensions, firstRow,
if (InventoryUtils.getValidRecipe(session, item, holder.inventory()::getItem, gridDimensions, firstRow,
height, firstCol, width) != null) {
// Recipe is already present on the client; don't send packet
return;
@ -178,7 +169,7 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
int index = 0;
for (int row = firstRow; row < height + firstRow; row++) {
for (int col = firstCol; col < width + firstCol; col++) {
GeyserItemStack geyserItemStack = inventory.getItem(col + (row * gridDimensions) + 1);
GeyserItemStack geyserItemStack = holder.inventory().getItem(col + (row * gridDimensions) + 1);
ingredients[index] = geyserItemStack.getItemData(session);
javaIngredients.add(geyserItemStack.asSlotDisplay());
@ -225,10 +216,11 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
}, 150, TimeUnit.MILLISECONDS));
}
static void updateSmithingTableOutput(GeyserSession session, int slot, ItemStack output, Inventory inventory) {
static void updateSmithingTableOutput(int slot, ItemStack output, InventoryHolder<?> holder) {
if (slot != SmithingInventoryTranslator.OUTPUT) {
return;
}
GeyserSession session = holder.session();
// Only process the most recent output result, and cancel the previous one.
if (session.getContainerOutputFuture() != null) {
@ -239,6 +231,7 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
return;
}
Inventory inventory = holder.inventory();
session.setContainerOutputFuture(session.scheduleInEventLoop(() -> {
GeyserItemStack template = inventory.getItem(SmithingInventoryTranslator.TEMPLATE);
if (template.asItem() != Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE) {
@ -285,11 +278,11 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
// Just set one of the slots to air, then right back to its proper item.
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(ContainerId.UI);
slotPacket.setSlot(InventoryUtils.getInventoryTranslator(session).javaSlotToBedrock(SmithingInventoryTranslator.MATERIAL));
slotPacket.setSlot(holder.translator().javaSlotToBedrock(SmithingInventoryTranslator.MATERIAL));
slotPacket.setItem(ItemData.AIR);
session.sendUpstreamPacket(slotPacket);
InventoryUtils.getInventoryTranslator(session).updateSlot(session, inventory, SmithingInventoryTranslator.MATERIAL);
holder.updateSlot(SmithingInventoryTranslator.MATERIAL);
}, 150, TimeUnit.MILLISECONDS));
}
}

View file

@ -38,6 +38,7 @@ import org.geysermc.geyser.entity.type.living.animal.horse.LlamaEntity;
import org.geysermc.geyser.entity.type.living.animal.horse.SkeletonHorseEntity;
import org.geysermc.geyser.entity.type.living.animal.horse.ZombieHorseEntity;
import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.inventory.horse.DonkeyInventoryTranslator;
@ -119,7 +120,7 @@ public class JavaHorseScreenOpenTranslator extends PacketTranslator<ClientboundH
// but everything is still indexed the same.
int slotCount = 2; // Don't depend on slot count sent from server
InventoryTranslator inventoryTranslator;
InventoryTranslator<Container> inventoryTranslator;
if (entity instanceof LlamaEntity llamaEntity) {
if (entity.getFlag(EntityFlag.CHESTED)) {
slotCount += llamaEntity.getStrength() * 3;
@ -153,6 +154,7 @@ public class JavaHorseScreenOpenTranslator extends PacketTranslator<ClientboundH
updateEquipPacket.setTag(builder.build());
session.sendUpstreamPacket(updateEquipPacket);
InventoryUtils.openInventory(session, new Container(session, entity.getNametag(), packet.getContainerId(), slotCount, null, session.getPlayerInventory(), inventoryTranslator));
Container container = new Container(session, entity.getNametag(), packet.getContainerId(), slotCount, null);
InventoryUtils.openInventory(new InventoryHolder<>(session, container, inventoryTranslator));
}
}

View file

@ -25,6 +25,7 @@
package org.geysermc.geyser.translator.protocol.java.inventory;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.VillagerTrade;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
@ -56,13 +57,18 @@ public class JavaMerchantOffersTranslator extends PacketTranslator<ClientboundMe
@Override
public void translate(GeyserSession session, ClientboundMerchantOffersPacket packet) {
Inventory openInventory = session.getOpenInventory();
if (!(openInventory instanceof MerchantContainer merchantInventory && openInventory.getJavaId() == packet.getContainerId())) {
InventoryHolder<?> holder = session.getInventoryHolder();
if (holder == null) {
return;
}
Inventory inventory = holder.inventory();
if (!(inventory instanceof MerchantContainer merchantInventory && inventory.getJavaId() == packet.getContainerId())) {
return;
}
// No previous inventory was closed -> no need of queuing the merchant inventory
if (!openInventory.isPending()) {
if (!holder.pending()) {
openMerchant(session, packet, merchantInventory);
return;
}
@ -94,7 +100,7 @@ public class JavaMerchantOffersTranslator extends PacketTranslator<ClientboundMe
updateTradePacket.setTradeTier(packet.getVillagerLevel() - 1);
updateTradePacket.setContainerId((short) packet.getContainerId());
updateTradePacket.setContainerType(ContainerType.TRADE);
updateTradePacket.setDisplayName(session.getOpenInventory().getTitle());
updateTradePacket.setDisplayName(merchantInventory.getTitle());
updateTradePacket.setSize(0);
updateTradePacket.setNewTradingUi(true);
updateTradePacket.setUsingEconomyTrade(true);

View file

@ -26,15 +26,15 @@
package org.geysermc.geyser.translator.protocol.java.inventory;
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.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,21 @@ public class JavaOpenBookTranslator extends PacketTranslator<ClientboundOpenBook
return;
}
if (stack.asItem().equals(Items.WRITTEN_BOOK)) {
Inventory openInventory = session.getOpenInventory();
// The item doesn't need to be a book; just needs to have either of these components.
if (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.getJavaId(), true);
InventoryUtils.sendJavaContainerClose(session, openInventory);
InventoryUtils.sendJavaContainerClose(openInventory);
InventoryUtils.closeInventory(session, openInventory, true);
}
InventoryTranslator translator = InventoryTranslator.inventoryTranslator(ContainerType.LECTERN);
//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");
Inventory inventory = translator.createInventory(session, "", FAKE_LECTERN_WINDOW_ID, ContainerType.LECTERN, session.getPlayerInventory());
((LecternContainer) inventory).setFakeLecternBook(stack, session);
InventoryUtils.openInventory(session, inventory);
LecternContainer container = translator.createInventory(session, "", FAKE_LECTERN_WINDOW_ID, ContainerType.LECTERN);
container.setVirtualLecternBook(stack, session);
InventoryUtils.openInventory(new InventoryHolder<>(session, container, translator));
}
}
}

View file

@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.protocol.java.inventory;
import net.kyori.adventure.text.Component;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.inventory.OldSmithingTableTranslator;
@ -51,8 +52,8 @@ public class JavaOpenScreenTranslator extends PacketTranslator<ClientboundOpenSc
return;
}
InventoryTranslator newTranslator;
Inventory openInventory = session.getOpenInventory();
InventoryTranslator<? extends Inventory> newTranslator;
InventoryHolder<? extends Inventory> currentInventory = session.getInventoryHolder();
// Hack: ViaVersion translates the old (pre 1.20) smithing table to a anvil (does not work for Bedrock). We can detect this and translate it back to a smithing table.
// (Implementation note: used to be a furnace. Was changed sometime before 1.21.2)
@ -64,8 +65,8 @@ public class JavaOpenScreenTranslator extends PacketTranslator<ClientboundOpenSc
// No translator exists for this window type. Close all windows and return.
if (newTranslator == null) {
if (openInventory != null) {
InventoryUtils.closeInventory(session, openInventory.getJavaId(), true);
if (currentInventory != null) {
InventoryUtils.closeInventory(session, currentInventory, true);
}
ServerboundContainerClosePacket closeWindowPacket = new ServerboundContainerClosePacket(packet.getContainerId());
@ -75,35 +76,26 @@ public class JavaOpenScreenTranslator extends PacketTranslator<ClientboundOpenSc
String name = MessageTranslator.convertMessage(packet.getTitle(), session.locale());
Inventory newInventory = newTranslator.createInventory(session, name, packet.getContainerId(), packet.getType(), session.getPlayerInventory());
if (openInventory != null) {
var newInventory = newTranslator.createInventory(session, name, packet.getContainerId(), packet.getType());
InventoryHolder<? extends Inventory> newInventoryHolder = new InventoryHolder<>(session, newInventory, newTranslator);
if (currentInventory != null) {
// Attempt to re-use existing open inventories, if possible.
// Pending inventories are also considered, as a Java server can re-request the same inventory.
if (newTranslator.canReuseInventory(session, newInventory, openInventory)) {
// Use the same Bedrock id
newInventory.setBedrockId(openInventory.getBedrockId());
// Also mirror other properties - in case we're e.g. dealing with a pending virtual inventory
boolean pending = openInventory.isPending();
newInventory.setDisplayed(openInventory.isDisplayed());
newInventory.setPending(pending);
newInventory.setHolderPosition(openInventory.getHolderPosition());
newInventory.setHolderId(openInventory.getHolderId());
session.setOpenInventory(newInventory);
GeyserImpl.getInstance().getLogger().debug(session, "Able to reuse current inventory. Is current pending? %s", pending);
if (newTranslator.canReuseInventory(session, newInventory, currentInventory.inventory())) {
newInventoryHolder.inheritFromExisting(currentInventory);
GeyserImpl.getInstance().getLogger().debug(session, "Able to reuse current inventory. Is current pending? %s", currentInventory.pending());
// If the current inventory is still pending, it'll be updated once open
if (newInventory.isDisplayed()) {
newTranslator.updateInventory(session, newInventory);
newInventoryHolder.updateInventory();
}
return;
}
InventoryUtils.closeInventory(session, openInventory.getJavaId(), true);
InventoryUtils.closeInventory(session, currentInventory, true);
}
InventoryUtils.openInventory(session, newInventory);
InventoryUtils.openInventory(newInventoryHolder);
}
}

View file

@ -28,7 +28,6 @@ package org.geysermc.geyser.translator.protocol.java.inventory;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.inventory.GeyserItemStack;
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.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundSetPlayerInventoryPacket;
@ -58,6 +57,6 @@ public class JavaSetPlayerInventoryTranslator extends PacketTranslator<Clientbou
GeyserItemStack newItem = GeyserItemStack.from(packet.getContents());
session.getBundleCache().initialize(newItem);
session.getPlayerInventory().setItem(slot, newItem, session);
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, session.getPlayerInventory(), slot);
session.getPlayerInventoryHolder().updateSlot(slot);
}
}

View file

@ -25,7 +25,6 @@
package org.geysermc.geyser.util;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.nbt.NbtMap;
@ -39,7 +38,7 @@ import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.inventory.GeyserItemStack;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.LecternContainer;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.geyser.inventory.click.Click;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
import org.geysermc.geyser.inventory.recipe.GeyserShapedRecipe;
@ -54,8 +53,6 @@ import org.geysermc.geyser.session.cache.registry.JavaRegistries;
import org.geysermc.geyser.session.cache.tags.Tag;
import org.geysermc.geyser.text.ChatColor;
import org.geysermc.geyser.text.GeyserLocale;
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.CompositeSlotDisplay;
@ -65,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;
@ -95,20 +93,20 @@ public class InventoryUtils {
* The main entrypoint to open an inventory. It will mark inventories as pending when the client isn't ready to
* open the new inventory yet.
*
* @param session the geyser session
* @param inventory the new inventory to open
* @param holder the new inventory to open
*/
public static void openInventory(GeyserSession session, Inventory inventory) {
session.setOpenInventory(inventory);
if (session.isClosingInventory() || !session.getUpstream().isInitialized() || session.getPendingOrCurrentBedrockInventoryId() != -1) {
public static void openInventory(InventoryHolder<?> holder) {
holder.markCurrent();
if (holder.shouldSetPending()) {
// Wait for close confirmation from client before opening the new inventory.
// Handled in BedrockContainerCloseTranslator
// or - client hasn't yet loaded in; wait until inventory is shown
inventory.setPending(true);
GeyserImpl.getInstance().getLogger().debug(session, "Inventory (%s) set pending: closing inv? %s, pending inv id? %s", debugInventory(inventory), session.isClosingInventory(), session.getPendingOrCurrentBedrockInventoryId());
holder.pending(true);
GeyserImpl.getInstance().getLogger().debug(holder.session(), "Inventory (%s) set pending: closing inv? %s, pending inv id? %s",
debugInventory(holder), holder.session().isClosingInventory(), holder.session().getPendingOrCurrentBedrockInventoryId());
return;
}
displayInventory(session, inventory);
displayInventory(holder);
}
/**
@ -117,71 +115,57 @@ public class InventoryUtils {
* occurred in the time. For example, a queued virtual inventory might be "outdated", so we wouldn't open it.
*/
public static void openPendingInventory(GeyserSession session) {
Inventory currentInventory = session.getOpenInventory();
if (currentInventory == null || !currentInventory.isPending()) {
InventoryHolder<?> holder = session.getInventoryHolder();
if (holder == null || !holder.pending()) {
session.setPendingOrCurrentBedrockInventoryId(-1);
GeyserImpl.getInstance().getLogger().debug(session, "No pending inventory, not opening an inventory! Current inventory: %s", debugInventory(currentInventory));
GeyserImpl.getInstance().getLogger().debug(session, "No pending inventory, not opening an inventory! Current inventory: %s", debugInventory(holder));
return;
}
// Current inventory isn't null! Let's see if we need to open it.
if (currentInventory.getBedrockId() == session.getPendingOrCurrentBedrockInventoryId()) {
GeyserImpl.getInstance().getLogger().debug(session, "Attempting to open currently delayed inventory with matching bedrock id! " + currentInventory.getBedrockId());
openAndUpdateInventory(session, currentInventory);
if (holder.inventory().getBedrockId() == session.getPendingOrCurrentBedrockInventoryId()) {
GeyserImpl.getInstance().getLogger().debug(session, "Attempting to open currently delayed inventory with matching bedrock id! " + holder.bedrockId());
openAndUpdateInventory(holder);
return;
}
GeyserImpl.getInstance().getLogger().debug(session, "Opening any pending inventory! " + debugInventory(currentInventory));
displayInventory(session, currentInventory);
GeyserImpl.getInstance().getLogger().debug(session, "Opening any pending inventory! " + debugInventory(holder));
displayInventory(holder);
}
/**
* Prepares and displays the current inventory. If necessary, it will queue the opening of virtual inventories.
* @param inventory the inventory to display
* @param holder the inventory to display
*/
public static void displayInventory(GeyserSession session, Inventory inventory) {
InventoryTranslator translator = inventory.getTranslator();
if (translator.prepareInventory(session, inventory)) {
session.setPendingOrCurrentBedrockInventoryId(inventory.getBedrockId());
if (translator.requiresOpeningDelay(session, inventory)) {
inventory.setPending(true);
public static void displayInventory(InventoryHolder<?> holder) {
if (holder.prepareInventory()) {
holder.session().setPendingOrCurrentBedrockInventoryId(holder.bedrockId());
if (holder.requiresOpeningDelay()) {
holder.pending(true);
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
latencyPacket.setFromServer(true);
latencyPacket.setTimestamp(MAGIC_VIRTUAL_INVENTORY_HACK);
session.sendUpstreamPacket(latencyPacket);
holder.session().sendUpstreamPacket(latencyPacket);
GeyserImpl.getInstance().getLogger().debug(session, "Queuing virtual inventory (%s)", debugInventory(inventory));
GeyserImpl.getInstance().getLogger().debug(holder.session(), "Queuing virtual inventory (%s)", debugInventory(holder));
} else {
openAndUpdateInventory(session, inventory);
openAndUpdateInventory(holder);
}
} else {
// Can occur if we e.g. did not find a spot to put a fake container in
session.setPendingOrCurrentBedrockInventoryId(-1);
sendJavaContainerClose(session, inventory);
session.setOpenInventory(null);
holder.session().setPendingOrCurrentBedrockInventoryId(-1);
sendJavaContainerClose(holder);
holder.session().setInventoryHolder(null);
}
}
/**
* Opens and updates an inventory, and resets no longer used inventory variables.
* Opens and updates an inventory
*/
public static void openAndUpdateInventory(GeyserSession session, Inventory inventory) {
inventory.getTranslator().openInventory(session, inventory);
inventory.getTranslator().updateInventory(session, inventory);
inventory.setDisplayed(true);
inventory.setPending(false);
}
/**
* Returns the current inventory translator.
*/
public static @NonNull InventoryTranslator getInventoryTranslator(GeyserSession session) {
Inventory inventory = session.getOpenInventory();
if (inventory == null) {
return InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR;
}
return inventory.getTranslator();
public static void openAndUpdateInventory(InventoryHolder<?> holder) {
holder.openInventory();
holder.updateInventory();
}
/**
@ -191,45 +175,47 @@ 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);
Inventory inventory = getInventory(session, javaId);
if (inventory != null) {
InventoryTranslator translator = inventory.getTranslator();
translator.closeInventory(session, inventory);
if (confirm && inventory.isDisplayed() && !inventory.isPending()
&& !(translator instanceof LecternInventoryTranslator) // Closing lecterns is not followed with a close confirmation
) {
if (holder != null) {
holder.closeInventory(confirm);
if (holder.shouldConfirmClose(confirm)) {
session.setClosingInventory(true);
}
session.getBundleCache().onInventoryClose(inventory);
GeyserImpl.getInstance().getLogger().debug(session, "Closed inventory: (java id: %s/bedrock id: %s), waiting on confirm? %s", inventory.getJavaId(), inventory.getBedrockId(), session.isClosingInventory());
session.getBundleCache().onInventoryClose(holder.inventory());
GeyserImpl.getInstance().getLogger().debug(session, "Closed inventory: (java id: %s/bedrock id: %s), waiting on confirm? %s", holder.javaId(), holder.bedrockId(), session.isClosingInventory());
}
session.setOpenInventory(null);
session.setInventoryHolder(null);
}
public static @Nullable Inventory getInventory(GeyserSession session, int javaId) {
/**
* 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) {
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 (session.getOpenInventory() instanceof LecternContainer) {
return session.getOpenInventory();
}
return session.getPlayerInventory();
return session.getPlayerInventoryHolder();
} else {
Inventory openInventory = session.getOpenInventory();
if (openInventory != null && javaId == openInventory.getJavaId()) {
return openInventory;
InventoryHolder<?> holder = session.getInventoryHolder();
if (holder != null && javaId == holder.javaId()) {
return holder;
}
return null;
}
}
public static void sendJavaContainerClose(GeyserSession session, Inventory inventory) {
if (inventory.shouldConfirmContainerClose()) {
ServerboundContainerClosePacket closeWindowPacket = new ServerboundContainerClosePacket(inventory.getJavaId());
session.sendDownstreamGamePacket(closeWindowPacket);
public static void sendJavaContainerClose(InventoryHolder<? extends Inventory> holder) {
if (holder.inventory().shouldConfirmContainerClose()) {
ServerboundContainerClosePacket closeWindowPacket = new ServerboundContainerClosePacket(holder.inventory().getJavaId());
holder.session().sendDownstreamGamePacket(closeWindowPacket);
}
}
@ -488,17 +474,18 @@ public class InventoryUtils {
return true;
}
public static String debugInventory(@Nullable Inventory inventory) {
if (inventory == null) {
public static String debugInventory(@Nullable InventoryHolder<? extends Inventory> holder) {
if (holder == null) {
return "null";
}
Inventory inventory = holder.inventory();
String inventoryType = inventory.getContainerType() != null ?
inventory.getContainerType().name() : "null";
return inventory.getClass().getSimpleName() + ": javaId=" + inventory.getJavaId() +
", bedrockId=" + inventory.getBedrockId() + ", size=" + inventory.getSize() +
", type=" + inventoryType + ", pending=" + inventory.isPending() +
", displayed=" + inventory.isPending() + ", delayed=" + inventory.isPending();
", type=" + inventoryType + ", pending=" + holder.pending() +
", displayed=" + inventory.isDisplayed();
}
}

View file

@ -75,7 +75,7 @@ public class LoginEncryptionUtils {
}
IdentityData extraData = result.identityClaims().extraData;
session.setAuthenticationData(new AuthData(extraData.displayName, extraData.identity, extraData.xuid));
session.setAuthData(new AuthData(extraData.displayName, extraData.identity, extraData.xuid));
session.setCertChainData(certChainData);
PublicKey identityPublicKey = result.identityClaims().parsedIdentityPublicKey();