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:
onebeastchris 2025-04-08 16:15:50 +02:00
parent e1abfe8dce
commit 68c2c00561
63 changed files with 594 additions and 404 deletions
core/src/main/java/org/geysermc/geyser
command
inventory
session
translator
util

View file

@ -45,6 +45,7 @@ import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.defaults.AdvancedTooltipsCommand;
import org.geysermc.geyser.command.defaults.AdvancementsCommand;
import org.geysermc.geyser.command.defaults.ConnectionTestCommand;
import org.geysermc.geyser.command.defaults.DebugCommand;
import org.geysermc.geyser.command.defaults.DumpCommand;
import org.geysermc.geyser.command.defaults.ExtensionsCommand;
import org.geysermc.geyser.command.defaults.HelpCommand;
@ -163,6 +164,7 @@ public class CommandRegistry implements EventRegistrar {
registerBuiltInCommand(new AdvancedTooltipsCommand("tooltips", "geyser.commands.advancedtooltips.desc", "geyser.command.tooltips"));
registerBuiltInCommand(new ConnectionTestCommand(geyser, "connectiontest", "geyser.commands.connectiontest.desc", "geyser.command.connectiontest"));
registerBuiltInCommand(new PingCommand("ping", "geyser.commands.ping.desc", "geyser.command.ping"));
registerBuiltInCommand(new DebugCommand("debug", "", "", TriState.TRUE, false, false));
if (this.geyser.getPlatformType() == PlatformType.STANDALONE) {
registerBuiltInCommand(new StopCommand(geyser, "stop", "geyser.commands.stop.desc", "geyser.command.stop"));
}

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

@ -0,0 +1,70 @@
/*
* 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.command.defaults;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId;
import org.cloudburstmc.protocol.bedrock.data.inventory.ItemData;
import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.util.TriState;
import org.geysermc.geyser.command.GeyserCommand;
import org.geysermc.geyser.command.GeyserCommandSource;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.inventory.chest.SingleChestInventoryTranslator;
import org.incendo.cloud.context.CommandContext;
public class DebugCommand extends GeyserCommand {
public DebugCommand(@NonNull String name, @NonNull String description, @NonNull String permission, @Nullable TriState permissionDefault, boolean playerOnly, boolean bedrockOnly) {
super(name, description, permission, permissionDefault, playerOnly, bedrockOnly);
}
@Override
public void execute(CommandContext<GeyserCommandSource> context) {
GeyserSession session = GeyserImpl.getInstance().getSessionManager().getAllSessions().get(0);
if (session != null) {
var holder = session.getOpenInventory();
if (holder != null && holder.translator() instanceof SingleChestInventoryTranslator) {
for (int i = 0; i <= holder.inventory().getSize(); i++) {
int bedrockSlot = holder.translator().javaSlotToBedrock(i);
GeyserImpl.getInstance().getLogger().info("java/bedrock slot: " + bedrockSlot);
InventorySlotPacket slotPacket = new InventorySlotPacket();
slotPacket.setContainerId(ContainerId.INVENTORY);
slotPacket.setSlot(bedrockSlot);
slotPacket.setItem(ItemData.builder()
.count(bedrockSlot)
.definition(session.getItemMappings().getStoredItems().glassBottle().getBedrockDefinition())
.build());
session.sendUpstreamPacket(slotPacket);
}
}
}
}
}

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;
@ -41,7 +40,7 @@ import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.S
* the expected level cost for AnvilInventoryUpdater
*/
@Getter @Setter
public class AnvilContainer extends Container<AnvilContainer> {
public class AnvilContainer extends Container {
/**
* Stores the level cost received as a window property from Java
*/
@ -63,8 +62,8 @@ public class AnvilContainer extends Container<AnvilContainer> {
private int lastTargetSlot = -1;
public AnvilContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator<AnvilContainer> 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,19 +25,18 @@
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
public class BeaconContainer extends Container<BeaconContainer> {
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<BeaconContainer> 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<CartographyContainer> {
public CartographyContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator<CartographyContainer> translator) {
super(session, title, id, size, containerType, playerInventory, translator);
public class CartographyContainer extends Container {
public CartographyContainer(GeyserSession session, String title, int id, int size, ContainerType containerType) {
super(session, title, id, size, containerType);
}
}

View file

@ -37,7 +37,7 @@ import org.jetbrains.annotations.Range;
* Combination of {@link Inventory} and {@link PlayerInventory}
*/
@Getter
public class Container<Type extends Container<Type>> extends Inventory<Type> {
public class Container extends Inventory {
protected final PlayerInventory playerInventory;
private final int containerSize;
@ -46,9 +46,9 @@ public class Container<Type extends Container<Type>> extends Inventory<Type> {
*/
private boolean isUsingRealBlock = false;
public Container(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator<Type> 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

@ -36,7 +36,7 @@ import org.geysermc.geyser.GeyserImpl;
import org.jetbrains.annotations.Range;
@Getter
public class CrafterContainer extends Container<CrafterContainer> {
public class CrafterContainer extends Container {
private GeyserItemStack resultItem = GeyserItemStack.EMPTY;
@Setter
@ -48,8 +48,8 @@ public class CrafterContainer extends Container<CrafterContainer> {
*/
private short disabledSlotsMask = 0;
public CrafterContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator<CrafterContainer> 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,13 +26,12 @@
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;
@Getter
public class EnchantingContainer extends Container<EnchantingContainer> {
public class EnchantingContainer extends Container {
/**
* A cache of what Bedrock sees
*/
@ -42,8 +41,8 @@ public class EnchantingContainer extends Container<EnchantingContainer> {
*/
private final GeyserEnchantOption[] geyserEnchantOptions;
public EnchantingContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator<EnchantingContainer> 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,20 +30,19 @@ 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
public class Generic3X3Container extends Container<Generic3X3Container> {
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<Generic3X3Container> 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

@ -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;
@ -44,7 +43,7 @@ import org.jetbrains.annotations.Range;
import java.util.Arrays;
@ToString
public abstract class Inventory<Type extends Inventory<Type>> {
public abstract class Inventory {
@Getter
protected final int javaId;
@ -95,44 +94,24 @@ public abstract class Inventory<Type extends Inventory<Type>> {
@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<Type> translator;
@Getter
private final GeyserSession session;
protected Inventory(GeyserSession session, int id, int size, ContainerType containerType, InventoryTranslator<Type> 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<Type> 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<Type extends Inventory<Type>> {
// 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.getOpenInventory() != null && session.getOpenInventory().bedrockId() == bedrockId) || session.isClosingInventory()) {
this.bedrockId += 1;
}
}
@ -214,36 +193,4 @@ public abstract class Inventory<Type extends Inventory<Type>> {
public boolean shouldConfirmContainerClose() {
return true;
}
/*
* Helper methods to avoid using the wrong translator to update specific inventories.
*/
public void updateInventory() {
this.translator.updateInventory(session, (Type) this);
}
public void updateProperty(int rawProperty, int value) {
this.translator.updateProperty(session, (Type) this, rawProperty, value);
}
public void updateSlot(int slot) {
this.translator.updateSlot(session, (Type) this, slot);
}
public void openInventory() {
this.translator.openInventory(session, (Type) this);
}
public void closeInventory() {
this.translator.closeInventory(session, (Type) this);
}
public boolean requiresOpeningDelay() {
return this.translator.requiresOpeningDelay(session, (Type) this);
}
public boolean prepareInventory() {
return this.translator.prepareInventory(session, (Type) this);
}
}

View file

@ -0,0 +1,174 @@
/*
* 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.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequestSlotData;
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.setOpenInventory(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() {
if (session.getOpenInventory() != this) {
throw new IllegalStateException("Inventory is not open!");
}
this.translator.openInventory(session, inventory);
this.pending = false;
this.inventory.setDisplayed(true);
}
public void closeInventory() {
if (session.getOpenInventory() != this) {
throw new IllegalStateException("Inventory is not open!");
}
this.translator.closeInventory(session, inventory);
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 void checkNetId(ItemStackRequestSlotData data) {
this.translator.checkNetId(session, inventory, data);
}
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

@ -31,12 +31,11 @@ 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;
@Getter
public class LecternContainer extends Container<LecternContainer> {
public class LecternContainer extends Container {
@Setter
private int currentBedrockPage = 0;
@Setter
@ -46,8 +45,8 @@ public class LecternContainer extends Container<LecternContainer> {
private boolean isBookInPlayerInventory = false;
public LecternContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator<LecternContainer> translator) {
super(session, title, id, size, containerType, playerInventory, translator);
public LecternContainer(GeyserSession session, String title, int id, int size, ContainerType containerType) {
super(session, title, id, size, containerType);
}
/**

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;
@ -38,7 +37,7 @@ import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.C
import java.util.List;
@Setter
public class MerchantContainer extends Container<MerchantContainer> {
public class MerchantContainer extends Container {
@Getter
private Entity villager;
private List<VillagerTrade> villagerTrades;
@ -47,8 +46,8 @@ public class MerchantContainer extends Container<MerchantContainer> {
@Getter
private int tradeExperience;
public MerchantContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator<MerchantContainer> 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,12 +31,11 @@ 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;
@Getter
public class PlayerInventory extends Inventory<PlayerInventory> {
public class PlayerInventory extends Inventory {
/**
* Stores the held item slot, starting at index 0.
* Add 36 in order to get the network item slot.
@ -48,7 +47,7 @@ public class PlayerInventory extends Inventory<PlayerInventory> {
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,19 +29,18 @@ 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
@Getter
public class StonecutterContainer extends Container<StonecutterContainer> {
public class StonecutterContainer extends Container {
/**
* The button that has currently been pressed Java-side
*/
private int stonecutterButton = -1;
public StonecutterContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator<StonecutterContainer> 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,8 +90,8 @@ 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;
}
}

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

@ -138,6 +138,7 @@ import org.geysermc.geyser.erosion.GeyserboundHandshakePacketHandler;
import org.geysermc.geyser.event.type.SessionDisconnectEventImpl;
import org.geysermc.geyser.impl.camera.CameraDefinitions;
import org.geysermc.geyser.impl.camera.GeyserCameraData;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.geyser.inventory.Inventory;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
@ -175,6 +176,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,10 +293,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
*/
private boolean isInWorldBorderWarningArea = false;
private final PlayerInventory playerInventory;
private final InventoryHolder<PlayerInventory> playerInventoryHolder;
@Setter
private @Nullable Inventory<?> openInventory;
private @Nullable InventoryHolder<? extends Inventory> openInventory;
@Setter
private boolean closingInventory;
@ -534,7 +537,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
/**
* 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)}
* Used to prevent our hacky player inventory closing workaround in {@link PlayerInventoryTranslator#closeInventory(GeyserSession, PlayerInventory)}
* to run when the closing is initated by the Bedrock client.
*/
@Setter
@ -681,15 +684,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,7 +717,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
this.playerEntity = new SessionPlayerEntity(this);
collisionManager.updatePlayerBoundingBox(this.playerEntity.getPosition());
this.playerInventory = new PlayerInventory(this);
this.playerInventoryHolder = new InventoryHolder<>(this, new PlayerInventory(this), InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
this.openInventory = null;
this.craftingRecipes = new Int2ObjectOpenHashMap<>();
this.javaToBedrockRecipeIds = new Int2ObjectOpenHashMap<>();
@ -1239,10 +1233,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 +1322,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 +1345,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 +1496,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
return true;
}
public PlayerInventory getPlayerInventory() {
return this.playerInventoryHolder.inventory();
}
@Override
public boolean sendForm(@NonNull FormBuilder<?, ?, ?> formBuilder) {
formCache.showForm(formBuilder.build());

View file

@ -83,8 +83,11 @@ public abstract class AbstractBlockInventoryTranslator<Type extends Container> e
}
@Override
public boolean canReuseInventory(GeyserSession session, @NonNull Type container, @NonNull Inventory previous) {
if (super.canReuseInventory(session, container, previous) && 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;

View file

@ -35,7 +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.PlayerInventory;
import org.geysermc.geyser.inventory.updater.AnvilInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession;
@ -101,8 +100,8 @@ public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator<A
}
@Override
public AnvilContainer 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

View file

@ -90,8 +90,8 @@ public abstract class BaseInventoryTranslator<Type extends Container> extends In
}
@Override
public Type createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
public Type createInventory(GeyserSession session, String name, int windowId, ContainerType containerType) {
//noinspection unchecked
return (Type) new Container(session, name, windowId, this.size, containerType, playerInventory, this);
return (Type) new Container(session, name, windowId, this.size, containerType);
}
}

View file

@ -39,7 +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.PlayerInventory;
import org.geysermc.geyser.inventory.holder.BlockInventoryHolder;
import org.geysermc.geyser.inventory.updater.UIInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
@ -143,8 +142,8 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator<
}
@Override
public BeaconContainer 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

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

@ -86,8 +86,8 @@ public class CartographyInventoryTranslator extends AbstractBlockInventoryTransl
}
@Override
public CartographyContainer 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

View file

@ -137,9 +137,9 @@ public class CrafterInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
public CrafterContainer 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) {

View file

@ -167,8 +167,8 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla
}
@Override
public EnchantingContainer 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

View file

@ -29,7 +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.PlayerInventory;
import org.geysermc.geyser.inventory.updater.ContainerInventoryUpdater;
import org.geysermc.geyser.level.block.Blocks;
import org.geysermc.geyser.session.GeyserSession;
@ -45,8 +44,8 @@ public class Generic3X3InventoryTranslator extends AbstractBlockInventoryTransla
}
@Override
public Generic3X3Container 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

View file

@ -145,44 +145,76 @@ public abstract class InventoryTranslator<Type extends Inventory> {
/**
* Whether a new inventory should be prepared - or if we can re-use the previous one.
*/
public boolean canReuseInventory(GeyserSession session, @NonNull Type inventory, @NonNull Inventory previous) {
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;
}
/**
* 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);
/**
* 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);
/**
* Converts a Java slot to the corresponding Bedrock container and slot
*/
public abstract BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot);
/**
* Returns the slot type for a Java slot id
*/
public abstract SlotType getSlotType(int javaSlot);
public abstract Type 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.

View file

@ -33,7 +33,6 @@ 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.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;
@ -132,7 +131,7 @@ public class LecternInventoryTranslator extends AbstractBlockInventoryTranslator
public void updateSlot(GeyserSession session, LecternContainer container, int slot) {
// If we're not in a real lectern, the Java server thinks we are still in the player inventory.
if (container.isBookInPlayerInventory()) {
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateSlot(session, session.getPlayerInventory(), slot);
session.getPlayerInventoryHolder().updateSlot(slot);
return;
}
super.updateSlot(session, container, slot);
@ -198,7 +197,7 @@ public class LecternInventoryTranslator extends AbstractBlockInventoryTranslator
}
@Override
public LecternContainer 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

@ -164,7 +164,8 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator<Merchan
// 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 (session.getOpenInventory() instanceof MerchantContainer merchantInventory) {
InventoryHolder<? extends Inventory> holder = session.getOpenInventory();
if (holder != null && holder.inventory() 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);
@ -191,7 +192,7 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator<Merchan
}
@Override
public MerchantContainer 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

@ -570,12 +570,12 @@ public class PlayerInventoryTranslator extends InventoryTranslator<PlayerInvento
}
@Override
public PlayerInventory 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();
}
@Override
public boolean canReuseInventory(GeyserSession session, @NonNull PlayerInventory inventory, @NonNull Inventory previous) {
public boolean canReuseInventory(GeyserSession session, @NonNull Inventory inventory, @NonNull Inventory previous) {
return true;
}

View file

@ -122,8 +122,8 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl
}
@Override
public StonecutterContainer 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

View file

@ -68,8 +68,9 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
* Mirrors {@link BlockInventoryHolder#canReuseContainer(GeyserSession, Container, Container)}
*/
@Override
public boolean canReuseInventory(GeyserSession session, @NonNull Container container, @NonNull Inventory oldInventory) {
if (!super.canReuseInventory(session, container, oldInventory) ||
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;
@ -85,8 +86,8 @@ 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(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;
}
}

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.getOpenInventory();
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() < 3) {
holder.incrementContainerOpenAttempts();
holder.pending(true);
session.scheduleInEventLoop(() -> {
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
@ -82,19 +84,18 @@ public class BedrockContainerCloseTranslator extends PacketTranslator<ContainerC
}
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.javaId(), 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,14 +42,15 @@ public class BedrockFilterTextTranslator extends PacketTranslator<FilterTextPack
@Override
public void translate(GeyserSession session, FilterTextPacket packet) {
if (session.getOpenInventory() instanceof CartographyContainer) {
InventoryHolder<?> holder = session.getOpenInventory();
if (holder != null && holder.inventory() instanceof CartographyContainer) {
// We don't want to be able to rename in the cartography table
return;
}
packet.setFromServer(true);
if (session.getOpenInventory() instanceof AnvilContainer anvilContainer) {
if (holder != null && holder.inventory() instanceof AnvilContainer anvilContainer) {
packet.setText(anvilContainer.checkForRename(session, packet.getText()));
anvilContainer.updateSlot(1);
holder.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) {
@ -601,7 +600,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator<Inve
// Update the player's inventory to remove any items added by the client itself
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,7 +26,7 @@
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.protocol.PacketTranslator;
import org.geysermc.geyser.translator.protocol.Translator;
@ -39,10 +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.getOpenInventory();
if (holder == null)
return;
inventory.getTranslator().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,14 +43,15 @@ 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.getOpenInventory();
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.sendJavaContainerClose(holder);
InventoryUtils.closeInventory(session, lecternContainer.getJavaId(), false);
} else {
// Each "page" Bedrock gives to us actually represents two pages (think opening a book and seeing two pages)
@ -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

@ -37,7 +37,7 @@ public class BedrockToggleCrafterSlotRequestTranslator extends PacketTranslator<
@Override
public void translate(GeyserSession session, ToggleCrafterSlotRequestPacket packet) {
if (!(session.getOpenInventory() instanceof CrafterContainer container)) {
if (session.getOpenInventory() == null || !(session.getOpenInventory().inventory() instanceof CrafterContainer container)) {
return;
}

View file

@ -25,13 +25,13 @@
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.InventoryHolder;
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 +52,8 @@ public class BedrockEntityEventTranslator extends PacketTranslator<EntityEventPa
session.sendDownstreamGamePacket(selectTradePacket);
session.scheduleInEventLoop(() -> {
Inventory openInventory = session.getOpenInventory();
if (openInventory instanceof MerchantContainer merchantInventory) {
InventoryHolder<?> openInventory = session.getOpenInventory();
if (openInventory != null && openInventory.inventory() instanceof MerchantContainer merchantInventory) {
merchantInventory.onTradeSelected(session, packet.getData());
}
}, 100, TimeUnit.MILLISECONDS);

View file

@ -128,7 +128,7 @@ public class BedrockInteractTranslator extends PacketTranslator<InteractPacket>
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.getOpenInventory()) {
return;
}

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,6 +25,7 @@
package org.geysermc.geyser.translator.protocol.java.inventory;
import org.geysermc.geyser.inventory.InventoryHolder;
import org.geysermc.geyser.inventory.PlayerInventory;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.translator.protocol.PacketTranslator;
@ -39,7 +40,8 @@ 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<?> holder = session.getOpenInventory();
session.setServerRequestedClosePlayerInventory(packet.getContainerId() == 0 && holder != null && holder.inventory() instanceof PlayerInventory);
InventoryUtils.closeInventory(session, packet.getContainerId(), (holder != null && holder.javaId() == packet.getContainerId()));
}
}

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
session.getPlayerInventory().updateInventory();
} else {
inventory.updateInventory();
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

@ -34,15 +34,13 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.descriptor.ItemDescripto
import org.cloudburstmc.protocol.bedrock.packet.CraftingDataPacket;
import org.cloudburstmc.protocol.bedrock.packet.InventorySlotPacket;
import org.geysermc.geyser.GeyserLogger;
import org.geysermc.geyser.inventory.Container;
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;
@ -63,16 +61,18 @@ import java.util.concurrent.TimeUnit;
@Translator(packet = ClientboundContainerSetSlotPacket.class)
public class JavaContainerSetSlotTranslator extends PacketTranslator<ClientboundContainerSetSlotPacket> {
// TODO
@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);
Inventory inventory = holder.inventory();
int slot = packet.getSlot();
if (slot >= inventory.getSize()) {
GeyserLogger logger = session.getGeyser().getLogger();
@ -86,23 +86,17 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
return;
}
if (translator instanceof SmithingInventoryTranslator) {
updateSmithingTableOutput(session, slot, packet.getItem(), (Container<?>) inventory);
if (holder.translator() instanceof SmithingInventoryTranslator) {
updateSmithingTableOutput(slot, packet.getItem(), holder);
} else {
updateCraftingGrid(session, slot, packet.getItem(), inventory, translator);
updateCraftingGrid(slot, packet.getItem(), holder);
}
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);
}
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();
@ -113,18 +107,20 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
/**
* 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);
@ -141,7 +137,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;
@ -162,7 +158,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;
@ -177,7 +173,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());
@ -224,10 +220,11 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
}, 150, TimeUnit.MILLISECONDS));
}
static void updateSmithingTableOutput(GeyserSession session, int slot, ItemStack output, Container<?> 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) {
@ -238,6 +235,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) {
@ -284,11 +282,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);
session.getOpenInventory().updateSlot(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.getOpenInventory();
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,7 +26,7 @@
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;
@ -60,20 +60,21 @@ public class JavaOpenBookTranslator extends PacketTranslator<ClientboundOpenBook
}
if (stack.asItem().equals(Items.WRITTEN_BOOK)) {
Inventory openInventory = session.getOpenInventory();
InventoryHolder<?> openInventory = session.getOpenInventory();
if (openInventory != null) {
InventoryUtils.closeInventory(session, openInventory.getJavaId(), true);
InventoryUtils.sendJavaContainerClose(session, openInventory);
InventoryUtils.closeInventory(session, openInventory.javaId(), true);
InventoryUtils.sendJavaContainerClose(openInventory);
}
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.setFakeLecternBook(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.getOpenInventory();
// 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.javaId(), 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.javaId(), 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,6 +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.InventoryHolder;
import org.geysermc.geyser.inventory.LecternContainer;
import org.geysermc.geyser.inventory.click.Click;
import org.geysermc.geyser.inventory.recipe.GeyserRecipe;
@ -54,7 +54,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.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;
@ -94,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);
}
/**
@ -116,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.getOpenInventory();
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 (inventory.prepareInventory()) {
session.setPendingOrCurrentBedrockInventoryId(inventory.getBedrockId());
if (inventory.requiresOpeningDelay()) {
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().setOpenInventory(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.openInventory();
inventory.updateInventory();
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();
}
/**
@ -193,41 +178,43 @@ public class InventoryUtils {
session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session);
updateCursor(session);
Inventory<?> inventory = getInventory(session, javaId);
if (inventory != null) {
inventory.closeInventory();
if (confirm && inventory.isDisplayed() && !inventory.isPending()
&& !(inventory instanceof LecternContainer) // Closing lecterns is not followed with a close confirmation
) {
InventoryHolder<?> holder = getInventory(session, javaId);
if (holder != null) {
holder.closeInventory();
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);
}
public static @Nullable Inventory<?> getInventory(GeyserSession session, int javaId) {
/**
* A util method to get the an inventory based on a Java id. This is used over
* {@link GeyserSession#getOpenInventory()} when needing to account for player inventories instead of just the open inventory.
*/
public static @Nullable InventoryHolder<?> getInventory(GeyserSession session, int javaId) {
InventoryHolder<?> holder = session.getOpenInventory();
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) {
if (holder != null && holder.inventory() instanceof LecternContainer) {
return session.getOpenInventory();
}
return session.getPlayerInventory();
return session.getPlayerInventoryHolder();
} else {
Inventory<?> openInventory = session.getOpenInventory();
if (openInventory != null && javaId == openInventory.getJavaId()) {
return openInventory;
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);
}
}
@ -486,17 +473,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();