diff --git a/README.md b/README.md
index 122ae35f1..3c1e18a81 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t
Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!
## Supported Versions
-Geyser is currently supporting Minecraft Bedrock 1.21.50 - 1.21.70 and Minecraft Java 1.21.5. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
+Geyser is currently supporting Minecraft Bedrock 1.21.50 - 1.21.71 and Minecraft Java 1.21.5. For more information, please see [here](https://geysermc.org/wiki/geyser/supported-versions/).
## Setting Up
Take a look [here](https://geysermc.org/wiki/geyser/setup/) for how to set up Geyser.
diff --git a/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java b/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java
index c83fce4c4..b6626aa6a 100644
--- a/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java
+++ b/api/src/main/java/org/geysermc/geyser/api/pack/PackCodec.java
@@ -98,7 +98,7 @@ public abstract class PackCodec {
* @since 2.1.1
*/
@NonNull
- public static PathPackCodec path(@NonNull Path path) {
+ public static PackCodec path(@NonNull Path path) {
return GeyserApi.api().provider(PathPackCodec.class, path);
}
@@ -110,7 +110,7 @@ public abstract class PackCodec {
* @since 2.6.2
*/
@NonNull
- public static UrlPackCodec url(@NonNull String url) {
+ public static PackCodec url(@NonNull String url) {
return GeyserApi.api().provider(UrlPackCodec.class, url);
}
}
diff --git a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java
index 9903d0d2e..da66b32c3 100644
--- a/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java
+++ b/bootstrap/mod/src/main/java/org/geysermc/geyser/platform/mod/GeyserModLogger.java
@@ -87,7 +87,7 @@ public class GeyserModLogger implements GeyserLogger {
@Override
public void debug(String message, Object... arguments) {
if (debug) {
- logger.info(message, arguments);
+ logger.info(String.format(message, arguments));
}
}
diff --git a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java
index 510ac0e79..8739add8a 100644
--- a/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java
+++ b/bootstrap/standalone/src/main/java/org/geysermc/geyser/platform/standalone/GeyserStandaloneLogger.java
@@ -117,7 +117,8 @@ public class GeyserStandaloneLogger extends SimpleTerminalConsole implements Gey
@Override
public void debug(String message, Object... arguments) {
- log.debug(ChatColor.GRAY + message, arguments);
+ // We can't use the debug call that would format for us as we're using Java's string formatting
+ log.debug(ChatColor.GRAY + String.format(message, arguments));
}
@Override
diff --git a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityLogger.java b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityLogger.java
index 5155d8958..0331b825e 100644
--- a/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityLogger.java
+++ b/bootstrap/velocity/src/main/java/org/geysermc/geyser/platform/velocity/GeyserVelocityLogger.java
@@ -77,7 +77,7 @@ public class GeyserVelocityLogger implements GeyserLogger {
@Override
public void debug(String message, Object... arguments) {
if (debug) {
- logger.info(message, arguments);
+ logger.info(String.format(message, arguments));
}
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java
index 92b50751a..5452d6d29 100644
--- a/core/src/main/java/org/geysermc/geyser/GeyserLogger.java
+++ b/core/src/main/java/org/geysermc/geyser/GeyserLogger.java
@@ -29,6 +29,7 @@ import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.geysermc.geyser.command.GeyserCommandSource;
+import org.geysermc.geyser.session.GeyserSession;
import java.util.UUID;
@@ -119,6 +120,15 @@ public interface GeyserLogger extends GeyserCommandSource {
*/
void setDebug(boolean debug);
+ /**
+ * A method to debug information specific to a session.
+ */
+ default void debug(GeyserSession session, String message, Object... arguments) {
+ if (isDebug()) {
+ debug("(" + session.bedrockUsername() + ") " + message, arguments);
+ }
+ }
+
/**
* If debug is enabled for this logger
*/
diff --git a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java
index 75b9252da..2de7d91e0 100644
--- a/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java
+++ b/core/src/main/java/org/geysermc/geyser/command/defaults/AdvancedTooltipsCommand.java
@@ -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.getInventoryTranslator().updateInventory(session, session.getPlayerInventory());
+ session.getPlayerInventory().updateInventory();
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java
index 03cb0b6e1..667b4190b 100644
--- a/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java
+++ b/core/src/main/java/org/geysermc/geyser/entity/type/BoatEntity.java
@@ -33,6 +33,7 @@ import org.cloudburstmc.protocol.bedrock.packet.AnimatePacket;
import org.cloudburstmc.protocol.bedrock.packet.MoveEntityAbsolutePacket;
import org.geysermc.geyser.entity.EntityDefinition;
import org.geysermc.geyser.entity.EntityDefinitions;
+import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.util.InteractionResult;
import org.geysermc.geyser.util.InteractiveTag;
@@ -86,8 +87,10 @@ public class BoatEntity extends Entity implements Leashable, Tickable {
@Override
protected void initializeMetadata() {
super.initializeMetadata();
- // Without this flag you cant stand on boats
- setFlag(EntityFlag.COLLIDABLE, true);
+ if (GameProtocol.is1_21_70orHigher(session)) {
+ // Without this flag you cant stand on boats
+ setFlag(EntityFlag.COLLIDABLE, true);
+ }
}
@Override
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java
index fe760c75a..08c64d267 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java
@@ -30,6 +30,7 @@ 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;
@@ -62,8 +63,8 @@ public class AnvilContainer extends Container {
private int lastTargetSlot = -1;
- public AnvilContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
- super(title, id, size, containerType, playerInventory);
+ public AnvilContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
+ super(session, title, id, size, containerType, playerInventory, translator);
}
/**
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/BeaconContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/BeaconContainer.java
index 1b59772fa..1869c474e 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/BeaconContainer.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/BeaconContainer.java
@@ -25,6 +25,8 @@
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;
@@ -35,7 +37,7 @@ public class BeaconContainer extends Container {
private int primaryId;
private int secondaryId;
- public BeaconContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
- super(title, id, size, containerType, playerInventory);
+ public BeaconContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
+ super(session, title, id, size, containerType, playerInventory, translator);
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/CartographyContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/CartographyContainer.java
index ace3f93ad..71c400643 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/CartographyContainer.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/CartographyContainer.java
@@ -25,10 +25,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;
public class CartographyContainer extends Container {
- public CartographyContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
- super(title, id, size, containerType, playerInventory);
+ public CartographyContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
+ super(session, title, id, size, containerType, playerInventory, translator);
}
}
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Container.java b/core/src/main/java/org/geysermc/geyser/inventory/Container.java
index f2db415c0..0a7a68bdb 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/Container.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/Container.java
@@ -46,8 +46,8 @@ public class Container extends Inventory {
*/
private boolean isUsingRealBlock = false;
- public Container(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
- super(title, id, size, containerType);
+ public Container(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
+ super(session, title, id, size, containerType, translator);
this.playerInventory = playerInventory;
this.containerSize = this.size + InventoryTranslator.PLAYER_INVENTORY_SIZE;
}
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/CrafterContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/CrafterContainer.java
index fb118252d..f9439cc69 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/CrafterContainer.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/CrafterContainer.java
@@ -48,8 +48,8 @@ public class CrafterContainer extends Container {
*/
private short disabledSlotsMask = 0;
- public CrafterContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
- super(title, id, size, containerType, playerInventory);
+ public CrafterContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
+ super(session, title, id, size, containerType, playerInventory, translator);
}
@Override
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/EnchantingContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/EnchantingContainer.java
index 08397ab44..1e4cb4355 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/EnchantingContainer.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/EnchantingContainer.java
@@ -25,24 +25,25 @@
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 {
/**
* A cache of what Bedrock sees
*/
- @Getter
private final EnchantOptionData[] enchantOptions;
/**
* A mutable cache of what the server sends us
*/
- @Getter
private final GeyserEnchantOption[] geyserEnchantOptions;
- public EnchantingContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
- super(title, id, size, containerType, playerInventory);
+ public EnchantingContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
+ super(session, title, id, size, containerType, playerInventory, translator);
enchantOptions = new EnchantOptionData[3];
geyserEnchantOptions = new GeyserEnchantOption[3];
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Generic3X3Container.java b/core/src/main/java/org/geysermc/geyser/inventory/Generic3X3Container.java
index 0b14d1105..2e1869f80 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/Generic3X3Container.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/Generic3X3Container.java
@@ -30,19 +30,20 @@ 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 {
/**
* Whether we need to set the container type as {@link org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType#DROPPER}.
*
* Used at {@link Generic3X3InventoryTranslator#openInventory(GeyserSession, Inventory)}
*/
- @Getter
private boolean isDropper = false;
- public Generic3X3Container(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
- super(title, id, size, containerType, playerInventory);
+ public Generic3X3Container(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
+ super(session, title, id, size, containerType, playerInventory, translator);
}
@Override
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java
index 2c0c2798d..5e49baac1 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/Inventory.java
@@ -32,8 +32,10 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.cloudburstmc.math.vector.Vector3i;
import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
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;
@@ -46,6 +48,10 @@ public abstract class Inventory {
@Getter
protected final int javaId;
+ @Setter
+ @Getter
+ private int bedrockId;
+
/**
* The Java inventory state ID from the server. As of Java Edition 1.18.1 this value has one instance per player.
* If this is out of sync with the server when a packet containing it is handled, the server will resync items.
@@ -55,7 +61,7 @@ public abstract class Inventory {
@Setter
private int stateId;
/**
- * See {@link org.geysermc.geyser.inventory.click.ClickPlan#execute(boolean)}; used as a hack
+ * See {@link ClickPlan#execute(boolean)}; used as a hack
*/
@Getter
private int nextStateId = -1;
@@ -75,43 +81,72 @@ public abstract class Inventory {
protected final GeyserItemStack[] items;
/**
- * The location of the inventory block. Will either be a fake block above the player's head, or the actual block location
+ * The location of the inventory block. Will either be a fake block above the player's head, or the actual block location.
*/
@Getter
@Setter
protected Vector3i holderPosition = Vector3i.ZERO;
+ /**
+ * The entity id of the entity holding the inventory.
+ * Either this, or the holder position must be set in order for Bedrock to open inventories.
+ */
@Getter
@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;
- protected Inventory(int id, int size, ContainerType containerType) {
- this("Inventory", id, size, containerType);
+ /**
+ * The translator for this inventory. Stored here to avoid de-syncs of the inventory and current translator.
+ */
+ @Getter
+ private final InventoryTranslator translator;
+
+ @Getter
+ private final GeyserSession session;
+
+ protected Inventory(GeyserSession session, int id, int size, ContainerType containerType, InventoryTranslator translator) {
+ this(session, "Inventory", id, size, containerType, translator);
}
- protected Inventory(String title, int javaId, int size, ContainerType containerType) {
+ protected Inventory(GeyserSession session, String title, int javaId, int size, ContainerType containerType, InventoryTranslator translator) {
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,
- // so this is rarely needed. (certain plugins)
- // Example: https://github.com/GeyserMC/Geyser/issues/3254
- public int getBedrockId() {
- return javaId <= 100 ? javaId : (javaId % 100) + 1;
+ // 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,
+ // so this is rarely needed. (certain plugins)
+ // Example: https://github.com/GeyserMC/Geyser/issues/3254
+ this.bedrockId = javaId <= 100 ? javaId : (javaId % 100) + 1;
+
+ // We occasionally need to re-open inventories with a delay in cases where
+ // 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()) {
+ this.bedrockId += 1;
+ }
}
public GeyserItemStack getItem(int slot) {
@@ -179,4 +214,20 @@ public abstract class Inventory {
public boolean shouldConfirmContainerClose() {
return true;
}
+
+ /*
+ * Helper methods to avoid using the wrong translator to update specific inventories.
+ */
+
+ public void updateInventory() {
+ this.translator.updateInventory(session, this);
+ }
+
+ public void updateProperty(int rawProperty, int value) {
+ this.translator.updateProperty(session, this, rawProperty, value);
+ }
+
+ public void updateSlot(int slot) {
+ this.translator.updateSlot(session, this, slot);
+ }
}
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/LecternContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/LecternContainer.java
index 9988188f1..c82b666f7 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/LecternContainer.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/LecternContainer.java
@@ -31,6 +31,7 @@ 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;
@@ -45,8 +46,8 @@ public class LecternContainer extends Container {
private boolean isBookInPlayerInventory = false;
- public LecternContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
- super(title, id, size, containerType, playerInventory);
+ public LecternContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
+ super(session, title, id, size, containerType, playerInventory, translator);
}
/**
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java
index 156d5e691..d3a149364 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/MerchantContainer.java
@@ -30,6 +30,7 @@ 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;
@@ -46,8 +47,8 @@ public class MerchantContainer extends Container {
@Getter
private int tradeExperience;
- public MerchantContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
- super(title, id, size, containerType, playerInventory);
+ public MerchantContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
+ super(session, title, id, size, containerType, playerInventory, translator);
}
public void onTradeSelected(GeyserSession session, int slot) {
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java
index 3ea9cd112..f3f68cb9e 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/PlayerInventory.java
@@ -31,24 +31,24 @@ 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 {
/**
* Stores the held item slot, starting at index 0.
* Add 36 in order to get the network item slot.
*/
- @Getter
@Setter
private int heldItemSlot;
- @Getter
@NonNull
private GeyserItemStack cursor = GeyserItemStack.EMPTY;
- public PlayerInventory() {
- super(0, 46, null);
+ public PlayerInventory(GeyserSession session) {
+ super(session, 0, 46, null, InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
heldItemSlot = 0;
}
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/StonecutterContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/StonecutterContainer.java
index 1eb115847..1f5f8f4b8 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/StonecutterContainer.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/StonecutterContainer.java
@@ -29,18 +29,19 @@ 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 {
/**
* The button that has currently been pressed Java-side
*/
- @Getter
- @Setter
private int stonecutterButton = -1;
- public StonecutterContainer(String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory) {
- super(title, id, size, containerType, playerInventory);
+ public StonecutterContainer(GeyserSession session, String title, int id, int size, ContainerType containerType, PlayerInventory playerInventory, InventoryTranslator translator) {
+ super(session, title, id, size, containerType, playerInventory, translator);
}
@Override
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/holder/BlockInventoryHolder.java b/core/src/main/java/org/geysermc/geyser/inventory/holder/BlockInventoryHolder.java
index 1bf30bc7e..8c905022b 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/holder/BlockInventoryHolder.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/holder/BlockInventoryHolder.java
@@ -40,11 +40,11 @@ import org.geysermc.geyser.level.block.type.Block;
import org.geysermc.geyser.level.block.type.BlockState;
import org.geysermc.geyser.registry.BlockRegistries;
import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.translator.inventory.InventoryTranslator;
import org.geysermc.geyser.util.InventoryUtils;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Objects;
import java.util.Set;
/**
@@ -77,24 +77,29 @@ public class BlockInventoryHolder extends InventoryHolder {
}
@Override
- public boolean prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
- // Check to see if there is an existing block we can use that the player just selected.
- // First, verify that the player's position has not changed, so we don't try to select a block wildly out of range.
- // (This could be a virtual inventory that the player is opening)
- if (checkInteractionPosition(session)) {
- // Then, check to see if the interacted block is valid for this inventory by ensuring the block state identifier is valid
- // and the bedrock block is vanilla
- BlockState state = session.getGeyser().getWorldManager().blockAt(session, session.getLastInteractionBlockPosition());
- if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(state.javaId())) {
- if (isValidBlock(state)) {
- // We can safely use this block
- inventory.setHolderPosition(session.getLastInteractionBlockPosition());
- ((Container) inventory).setUsingRealBlock(true, state.block());
- setCustomName(session, session.getLastInteractionBlockPosition(), inventory, state);
+ public boolean canReuseContainer(GeyserSession session, Container container, Container previous) {
+ // We already ensured that the inventories are using the same type, size, and title
- return true;
- }
- }
+ // TODO this would currently break, so we're not reusing this
+ if (previous.isUsingRealBlock()) {
+ return false;
+ }
+
+ // Check if we'd be using the same virtual inventory position.
+ Vector3i position = InventoryUtils.findAvailableWorldSpace(session);
+ 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);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean prepareInventory(GeyserSession session, Container inventory) {
+ if (canUseRealBlock(session, inventory)) {
+ return true;
}
Vector3i position = InventoryUtils.findAvailableWorldSpace(session);
@@ -115,6 +120,29 @@ public class BlockInventoryHolder extends InventoryHolder {
return true;
}
+ protected boolean canUseRealBlock(GeyserSession session, Container inventory) {
+ // Check to see if there is an existing block we can use that the player just selected.
+ // First, verify that the player's position has not changed, so we don't try to select a block wildly out of range.
+ // (This could be a virtual inventory that the player is opening)
+ if (checkInteractionPosition(session)) {
+ // Then, check to see if the interacted block is valid for this inventory by ensuring the block state identifier is valid
+ // and the bedrock block is vanilla
+ BlockState state = session.getGeyser().getWorldManager().blockAt(session, session.getLastInteractionBlockPosition());
+ if (!BlockRegistries.CUSTOM_BLOCK_STATE_OVERRIDES.get().containsKey(state.javaId())) {
+ if (isValidBlock(state)) {
+ // We can safely use this block
+ inventory.setHolderPosition(session.getLastInteractionBlockPosition());
+ inventory.setUsingRealBlock(true, state.block());
+ setCustomName(session, session.getLastInteractionBlockPosition(), inventory, state);
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
/**
* Will be overwritten in the beacon inventory translator to remove the check, since virtual inventories can't exist.
*
@@ -145,57 +173,49 @@ public class BlockInventoryHolder extends InventoryHolder {
}
@Override
- public void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
+ public void openInventory(GeyserSession session, Container container) {
ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket();
- containerOpenPacket.setId((byte) inventory.getBedrockId());
+ containerOpenPacket.setId((byte) container.getBedrockId());
containerOpenPacket.setType(containerType);
- containerOpenPacket.setBlockPosition(inventory.getHolderPosition());
- containerOpenPacket.setUniqueEntityId(inventory.getHolderId());
+ containerOpenPacket.setBlockPosition(container.getHolderPosition());
+ containerOpenPacket.setUniqueEntityId(container.getHolderId());
session.sendUpstreamPacket(containerOpenPacket);
+
+ GeyserImpl.getInstance().getLogger().debug(session, containerOpenPacket.toString());
}
@Override
- public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory, ContainerType type) {
- if (!(inventory instanceof Container container)) {
- GeyserImpl.getInstance().getLogger().warning("Tried to close a non-container inventory in a block inventory holder! Please report this error on discord.");
- GeyserImpl.getInstance().getLogger().warning("Current inventory translator: " + translator.getClass().getSimpleName());
- GeyserImpl.getInstance().getLogger().warning("Current inventory: " + inventory.getClass().getSimpleName());
- // Try to save ourselves? maybe?
- // https://github.com/GeyserMC/Geyser/issues/4141
- // TODO: improve once this issue is pinned down
- session.setOpenInventory(null);
- session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
- return;
- }
+ public void closeInventory(GeyserSession session, Container container, ContainerType type) {
+ if (container.isDisplayed() && !(container instanceof LecternContainer)) {
+ // No need to reset a block since we didn't change any blocks
+ // But send a container close packet because we aren't destroying the original.
+ ContainerClosePacket packet = new ContainerClosePacket();
+ packet.setId((byte) container.getBedrockId());
+ packet.setServerInitiated(true);
+ packet.setType(type != null ? type : containerType);
+ session.sendUpstreamPacket(packet);
- // Bedrock broke inventory closing. I wish i was kidding.
- // "type" is explicitly passed to keep track of which inventory types can be closed without
- // ""workarounds"". yippie.
- // Further, Lecterns cannot be closed with any of the two methods below.
- if (container.isUsingRealBlock() && !(container instanceof LecternContainer)) {
- if (type != null) {
- // No need to reset a block since we didn't change any blocks
- // But send a container close packet because we aren't destroying the original.
- ContainerClosePacket packet = new ContainerClosePacket();
- packet.setId((byte) inventory.getBedrockId());
- packet.setServerInitiated(true);
- packet.setType(type);
- session.sendUpstreamPacket(packet);
- return;
+ if (container.isUsingRealBlock()) {
+ // Type being null indicates that the ContainerClosePacket is not effective.
+ // So we yeet away the block!
+ if (type == null) {
+ Vector3i holderPos = container.getHolderPosition();
+ UpdateBlockPacket blockPacket = new UpdateBlockPacket();
+ blockPacket.setDataLayer(0);
+ blockPacket.setBlockPosition(holderPos);
+ blockPacket.setDefinition(session.getBlockMappings().getBedrockAir());
+ blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
+ session.sendUpstreamPacket(blockPacket);
+ } else {
+ // We're using a real block and are able to close the block without destroying it,
+ // so we can don't need to reset it below.
+ return;
+ }
}
-
- // Destroy the block. There's no inventory to view => it gets closed!
- Vector3i holderPos = inventory.getHolderPosition();
- UpdateBlockPacket blockPacket = new UpdateBlockPacket();
- blockPacket.setDataLayer(0);
- blockPacket.setBlockPosition(holderPos);
- blockPacket.setDefinition(session.getBlockMappings().getBedrockAir());
- blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
- session.sendUpstreamPacket(blockPacket);
}
// Reset to correct block
- Vector3i holderPos = inventory.getHolderPosition();
+ Vector3i holderPos = container.getHolderPosition();
int realBlock = session.getGeyser().getWorldManager().getBlockAt(session, holderPos.getX(), holderPos.getY(), holderPos.getZ());
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
blockPacket.setDataLayer(0);
diff --git a/core/src/main/java/org/geysermc/geyser/inventory/holder/InventoryHolder.java b/core/src/main/java/org/geysermc/geyser/inventory/holder/InventoryHolder.java
index d61193c7a..6cce5b4a9 100644
--- a/core/src/main/java/org/geysermc/geyser/inventory/holder/InventoryHolder.java
+++ b/core/src/main/java/org/geysermc/geyser/inventory/holder/InventoryHolder.java
@@ -26,12 +26,12 @@
package org.geysermc.geyser.inventory.holder;
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
-import org.geysermc.geyser.inventory.Inventory;
+import org.geysermc.geyser.inventory.Container;
import org.geysermc.geyser.session.GeyserSession;
-import org.geysermc.geyser.translator.inventory.InventoryTranslator;
public abstract class InventoryHolder {
- public abstract boolean prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory);
- public abstract void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory);
- public abstract void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory, ContainerType containerType);
+ public abstract boolean canReuseContainer(GeyserSession session, Container inventory, Container oldInventory);
+ public abstract boolean prepareInventory(GeyserSession session, Container inventory);
+ public abstract void openInventory(GeyserSession session, Container inventory);
+ public abstract void closeInventory(GeyserSession session, Container inventory, ContainerType containerType);
}
diff --git a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java
index 95452486b..a3e2ac6ac 100644
--- a/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java
+++ b/core/src/main/java/org/geysermc/geyser/network/GameProtocol.java
@@ -98,6 +98,10 @@ public final class GameProtocol {
return protocolVersion < 776;
}
+ public static boolean is1_21_70orHigher(GeyserSession session) {
+ return session.protocolVersion() >= Bedrock_v786.CODEC.getProtocolVersion();
+ }
+
/**
* Gets the {@link PacketCodec} for Minecraft: Java Edition.
*
diff --git a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
index 096ea4353..e5f71ce05 100644
--- a/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
+++ b/core/src/main/java/org/geysermc/geyser/session/GeyserSession.java
@@ -175,7 +175,6 @@ 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.text.MessageTranslator;
import org.geysermc.geyser.util.ChunkUtils;
import org.geysermc.geyser.util.EntityUtils;
@@ -292,13 +291,19 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private boolean isInWorldBorderWarningArea = false;
private final PlayerInventory playerInventory;
+
@Setter
private @Nullable Inventory openInventory;
+
@Setter
private boolean closingInventory;
+ /**
+ * Stores the bedrock inventory id of the pending inventory, or -1 if no inventory is pending.
+ * This id is only set when the block that should be opened exists.
+ */
@Setter
- private @NonNull InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR;
+ private int pendingOrCurrentBedrockInventoryId = -1;
/**
* Use {@link #getNextItemNetId()} instead for consistency
@@ -676,6 +681,14 @@ 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;
@@ -710,7 +723,7 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
this.playerEntity = new SessionPlayerEntity(this);
collisionManager.updatePlayerBoundingBox(this.playerEntity.getPosition());
- this.playerInventory = new PlayerInventory();
+ this.playerInventory = new PlayerInventory(this);
this.openInventory = null;
this.craftingRecipes = new Int2ObjectOpenHashMap<>();
this.javaToBedrockRecipeIds = new Int2ObjectOpenHashMap<>();
diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/FormCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/FormCache.java
index 3f7df97c1..2f5d5e517 100644
--- a/core/src/main/java/org/geysermc/geyser/session/cache/FormCache.java
+++ b/core/src/main/java/org/geysermc/geyser/session/cache/FormCache.java
@@ -25,23 +25,32 @@
package org.geysermc.geyser.session.cache;
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import lombok.RequiredArgsConstructor;
import org.cloudburstmc.protocol.bedrock.packet.ModalFormRequestPacket;
import org.cloudburstmc.protocol.bedrock.packet.ModalFormResponsePacket;
import org.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket;
-import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
-import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
-import lombok.RequiredArgsConstructor;
+import org.geysermc.cumulus.component.util.ComponentType;
+import org.geysermc.cumulus.form.CustomForm;
import org.geysermc.cumulus.form.Form;
import org.geysermc.cumulus.form.SimpleForm;
import org.geysermc.cumulus.form.impl.FormDefinitions;
import org.geysermc.geyser.GeyserImpl;
+import org.geysermc.geyser.network.GameProtocol;
import org.geysermc.geyser.session.GeyserSession;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
@RequiredArgsConstructor
public class FormCache {
+ private static final Gson GSON_TEMP = new Gson();
/**
* The magnitude of this doesn't actually matter, but it must be negative so that
@@ -100,9 +109,52 @@ public class FormCache {
return;
}
+ String responseData = response.getFormData();
+ //todo work on a proper solution in Cumulus, but that'd require all Floodgate instances to update as well and
+ // drops support for older Bedrock versions (because Cumulus isn't made to support multiple versions). That's
+ // why this hotfix exists.
+ if (form instanceof CustomForm customForm && GameProtocol.is1_21_70orHigher(session)) {
+ // Labels are no longer included as a json null, so we have to manually add them for now.
+ IntList labelIndexes = new IntArrayList();
+ for (int i = 0; i < customForm.content().size(); i++) {
+ var component = customForm.content().get(i);
+ if (component == null) {
+ continue;
+ }
+ if (component.type() == ComponentType.LABEL) {
+ labelIndexes.add(i);
+ }
+ }
+ if (!labelIndexes.isEmpty()) {
+ // If the form only has labels, the response is the literal
+ // null (with a newline char) instead of a json array
+ if (responseData.startsWith("null")) {
+ List