mirror of
https://github.com/GeyserMC/Geyser.git
synced 2025-12-19 14:59:27 +00:00
Indicate 1.21.71 support in readme
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,9 +87,11 @@ public class BoatEntity extends Entity implements Leashable, Tickable {
|
||||
@Override
|
||||
protected void initializeMetadata() {
|
||||
super.initializeMetadata();
|
||||
if (GameProtocol.is1_21_70orHigher(session)) {
|
||||
// Without this flag you cant stand on boats
|
||||
setFlag(EntityFlag.COLLIDABLE, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveAbsolute(Vector3f position, float yaw, float pitch, float headYaw, boolean isOnGround, boolean teleported) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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}.
|
||||
* <p>
|
||||
* 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
|
||||
|
||||
@@ -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.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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) inventory.getBedrockId());
|
||||
packet.setId((byte) container.getBedrockId());
|
||||
packet.setServerInitiated(true);
|
||||
packet.setType(type);
|
||||
packet.setType(type != null ? type : containerType);
|
||||
session.sendUpstreamPacket(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
// Destroy the block. There's no inventory to view => it gets closed!
|
||||
Vector3i holderPos = inventory.getHolderPosition();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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<Object> newResponse = new ArrayList<>();
|
||||
for (int i = 0; i < labelIndexes.size(); i++) {
|
||||
newResponse.add(null);
|
||||
}
|
||||
responseData = GSON_TEMP.toJson(newResponse);
|
||||
} else {
|
||||
JsonArray responseDataArray = GSON_TEMP.fromJson(responseData, JsonArray.class);
|
||||
List<Object> newResponse = new ArrayList<>();
|
||||
|
||||
int handledLabelCount = 0;
|
||||
for (int i = 0; i < responseDataArray.size() + labelIndexes.size(); i++) {
|
||||
if (labelIndexes.contains(i)) {
|
||||
newResponse.add(null);
|
||||
handledLabelCount++;
|
||||
continue;
|
||||
}
|
||||
newResponse.add(responseDataArray.get(i - handledLabelCount));
|
||||
}
|
||||
responseData = GSON_TEMP.toJson(newResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
formDefinitions.definitionFor(form)
|
||||
.handleFormResponse(form, response.getFormData());
|
||||
.handleFormResponse(form, responseData);
|
||||
} catch (Exception e) {
|
||||
GeyserImpl.getInstance().getLogger().error("Error while processing form response!", e);
|
||||
}
|
||||
|
||||
@@ -25,8 +25,10 @@
|
||||
|
||||
package org.geysermc.geyser.translator.inventory;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
|
||||
import org.geysermc.geyser.inventory.Container;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.holder.BlockInventoryHolder;
|
||||
import org.geysermc.geyser.inventory.holder.InventoryHolder;
|
||||
@@ -75,19 +77,34 @@ public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTran
|
||||
this.updater = updater;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresOpeningDelay(GeyserSession session, Inventory inventory) {
|
||||
return inventory instanceof Container container && !container.isUsingRealBlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canReuseInventory(GeyserSession session, @NonNull Inventory inventory, @NonNull Inventory previous) {
|
||||
if (super.canReuseInventory(session, inventory, previous)
|
||||
&& inventory instanceof Container container
|
||||
&& previous instanceof Container previousContainer) {
|
||||
return holder.canReuseContainer(session, container, previousContainer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
|
||||
return holder.prepareInventory(this, session, inventory);
|
||||
return holder.prepareInventory(session, (Container) inventory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openInventory(GeyserSession session, Inventory inventory) {
|
||||
holder.openInventory(this, session, inventory);
|
||||
holder.openInventory(session, (Container) inventory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeInventory(GeyserSession session, Inventory inventory) {
|
||||
holder.closeInventory(this, session, inventory, closeContainerType(inventory));
|
||||
holder.closeInventory(session, (Container) inventory, closeContainerType(inventory));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -103,8 +103,8 @@ public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new AnvilContainer(name, windowId, this.size, containerType, playerInventory);
|
||||
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new AnvilContainer(session, name, windowId, this.size, containerType, playerInventory, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -90,7 +90,7 @@ public abstract class BaseInventoryTranslator extends InventoryTranslator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new Container(name, windowId, this.size, containerType, playerInventory);
|
||||
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new Container(session, name, windowId, this.size, containerType, playerInventory, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.response.ItemS
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BlockEntityDataPacket;
|
||||
import org.geysermc.geyser.inventory.BeaconContainer;
|
||||
import org.geysermc.geyser.inventory.BedrockContainerSlot;
|
||||
import org.geysermc.geyser.inventory.Container;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.PlayerInventory;
|
||||
import org.geysermc.geyser.inventory.holder.BlockInventoryHolder;
|
||||
@@ -61,12 +62,12 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) {
|
||||
if (!((BeaconContainer) inventory).isUsingRealBlock()) {
|
||||
InventoryUtils.closeInventory(session, inventory.getJavaId(), false);
|
||||
public void openInventory(GeyserSession session, Container container) {
|
||||
if (!container.isUsingRealBlock()) {
|
||||
InventoryUtils.closeInventory(session, container.getJavaId(), false);
|
||||
return;
|
||||
}
|
||||
super.openInventory(translator, session, inventory);
|
||||
super.openInventory(session, container);
|
||||
}
|
||||
}, UIInventoryUpdater.INSTANCE);
|
||||
}
|
||||
@@ -144,8 +145,8 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new BeaconContainer(name, windowId, this.size, containerType, playerInventory);
|
||||
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new BeaconContainer(session, name, windowId, this.size, containerType, playerInventory, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -86,8 +86,8 @@ public class CartographyInventoryTranslator extends AbstractBlockInventoryTransl
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new CartographyContainer(name, windowId, this.size, containerType, playerInventory);
|
||||
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new CartographyContainer(session, name, windowId, this.size, containerType, playerInventory, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -139,9 +139,9 @@ public class CrafterInventoryTranslator extends AbstractBlockInventoryTranslator
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
// Java sends the triggered and slot bits incrementally through properties, which we store here
|
||||
return new CrafterContainer(name, windowId, this.size, containerType, playerInventory);
|
||||
return new CrafterContainer(session, name, windowId, this.size, containerType, playerInventory, this);
|
||||
}
|
||||
|
||||
private static void updateBlockEntity(GeyserSession session, CrafterContainer container) {
|
||||
|
||||
@@ -169,8 +169,8 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new EnchantingContainer(name, windowId, this.size, containerType, playerInventory);
|
||||
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new EnchantingContainer(session, name, windowId, this.size, containerType, playerInventory, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -46,8 +46,8 @@ public class Generic3X3InventoryTranslator extends AbstractBlockInventoryTransla
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new Generic3X3Container(name, windowId, this.size, containerType, playerInventory);
|
||||
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new Generic3X3Container(session, name, windowId, this.size, containerType, playerInventory, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,7 +35,9 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSortedSet;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.FullContainerName;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.ItemStackRequest;
|
||||
@@ -85,6 +87,7 @@ import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.geysermc.geyser.translator.inventory.BundleInventoryTranslator.isBundle;
|
||||
|
||||
@@ -134,6 +137,41 @@ public abstract class InventoryTranslator {
|
||||
|
||||
public final int size;
|
||||
|
||||
// Whether the inventory open should be delayed.
|
||||
public boolean requiresOpeningDelay(GeyserSession session, Inventory inventory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a new inventory should be prepared - or if we can re-use the previous one.
|
||||
*/
|
||||
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())
|
||||
) {
|
||||
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;
|
||||
}
|
||||
public abstract boolean prepareInventory(GeyserSession session, Inventory inventory);
|
||||
public abstract void openInventory(GeyserSession session, Inventory inventory);
|
||||
public abstract void closeInventory(GeyserSession session, Inventory inventory);
|
||||
@@ -144,7 +182,7 @@ public abstract class InventoryTranslator {
|
||||
public abstract int javaSlotToBedrock(int javaSlot);
|
||||
public abstract BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot);
|
||||
public abstract SlotType getSlotType(int javaSlot);
|
||||
public abstract Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory);
|
||||
public abstract Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory);
|
||||
|
||||
/**
|
||||
* Used for crafting-related transactions. Will override in PlayerInventoryTranslator and CraftingInventoryTranslator.
|
||||
|
||||
@@ -199,7 +199,7 @@ public class LecternInventoryTranslator extends AbstractBlockInventoryTranslator
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new LecternContainer(name, windowId, this.size + playerInventory.getSize(), containerType, playerInventory);
|
||||
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new LecternContainer(session, name, windowId, this.size + playerInventory.getSize(), containerType, playerInventory, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new MerchantContainer(name, windowId, this.size, containerType, playerInventory);
|
||||
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new MerchantContainer(session, name, windowId, this.size, containerType, playerInventory, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ package org.geysermc.geyser.translator.inventory;
|
||||
import it.unimi.dsi.fastutil.ints.IntIterator;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.protocol.bedrock.data.definitions.BlockDefinition;
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerId;
|
||||
@@ -569,10 +570,15 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canReuseInventory(GeyserSession session, @NonNull Inventory inventory, @NonNull Inventory previous) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
|
||||
return true;
|
||||
|
||||
@@ -123,8 +123,8 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl
|
||||
}
|
||||
|
||||
@Override
|
||||
public Inventory createInventory(String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new StonecutterContainer(name, windowId, this.size, containerType, playerInventory);
|
||||
public Inventory createInventory(GeyserSession session, String name, int windowId, ContainerType containerType, PlayerInventory playerInventory) {
|
||||
return new StonecutterContainer(session, name, windowId, this.size, containerType, playerInventory, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -27,6 +27,7 @@ package org.geysermc.geyser.translator.inventory.chest;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType;
|
||||
import org.geysermc.geyser.inventory.BedrockContainerSlot;
|
||||
import org.geysermc.geyser.inventory.Container;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.updater.ChestInventoryUpdater;
|
||||
import org.geysermc.geyser.inventory.updater.InventoryUpdater;
|
||||
@@ -51,6 +52,11 @@ public abstract class ChestInventoryTranslator extends BaseInventoryTranslator {
|
||||
return bedrockDestinationContainer == ContainerSlotType.LEVEL_ENTITY && javaDestinationSlot >= this.size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresOpeningDelay(GeyserSession session, Inventory inventory) {
|
||||
return inventory instanceof Container container && !container.isUsingRealBlock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateInventory(GeyserSession session, Inventory inventory) {
|
||||
updater.updateInventory(this, session, inventory);
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
package org.geysermc.geyser.translator.inventory.chest;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.cloudburstmc.math.vector.Vector3i;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
@@ -37,6 +38,7 @@ import org.cloudburstmc.protocol.bedrock.packet.UpdateBlockPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.inventory.Container;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.holder.BlockInventoryHolder;
|
||||
import org.geysermc.geyser.level.block.Blocks;
|
||||
import org.geysermc.geyser.level.block.property.ChestType;
|
||||
import org.geysermc.geyser.level.block.property.Properties;
|
||||
@@ -44,11 +46,12 @@ import org.geysermc.geyser.level.block.type.BlockState;
|
||||
import org.geysermc.geyser.level.physics.Direction;
|
||||
import org.geysermc.geyser.registry.BlockRegistries;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.level.block.entity.BlockEntityTranslator;
|
||||
import org.geysermc.geyser.translator.level.block.entity.DoubleChestBlockEntityTranslator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
|
||||
private final int defaultJavaBlockState;
|
||||
|
||||
@@ -60,8 +63,154 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
|
||||
.javaId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional checks to verify that we can re-use the block inventory holder.
|
||||
* Mirrors {@link BlockInventoryHolder#canReuseContainer(GeyserSession, Container, Container)}
|
||||
*/
|
||||
@Override
|
||||
public boolean canReuseInventory(GeyserSession session, @NonNull Inventory inventory, @NonNull Inventory oldInventory) {
|
||||
if (!super.canReuseInventory(session, inventory, oldInventory) ||
|
||||
!(inventory instanceof Container container) ||
|
||||
!(oldInventory instanceof Container previous)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME - but these aren't the reason we have 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(inventory), previous.getHolderPosition(), position);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
|
||||
if (canUseRealBlock(session, inventory)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector3i position = InventoryUtils.findAvailableWorldSpace(session);
|
||||
if (position == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3i pairPosition = position.add(Vector3i.UNIT_X);
|
||||
BlockDefinition definition = session.getBlockMappings().getVanillaBedrockBlock(defaultJavaBlockState);
|
||||
|
||||
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
|
||||
blockPacket.setDataLayer(0);
|
||||
blockPacket.setBlockPosition(position);
|
||||
blockPacket.setDefinition(definition);
|
||||
blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
|
||||
session.sendUpstreamPacket(blockPacket);
|
||||
|
||||
NbtMapBuilder tag = BlockEntityTranslator.getConstantBedrockTag("Chest", position)
|
||||
.putInt("pairx", pairPosition.getX())
|
||||
.putInt("pairz", pairPosition.getZ())
|
||||
.putString("CustomName", inventory.getTitle())
|
||||
.putBoolean("pairlead", false);
|
||||
|
||||
BlockEntityDataPacket dataPacket = new BlockEntityDataPacket();
|
||||
dataPacket.setData(tag.build());
|
||||
dataPacket.setBlockPosition(position);
|
||||
session.sendUpstreamPacket(dataPacket);
|
||||
|
||||
blockPacket = new UpdateBlockPacket();
|
||||
blockPacket.setDataLayer(0);
|
||||
blockPacket.setBlockPosition(pairPosition);
|
||||
blockPacket.setDefinition(definition);
|
||||
blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
|
||||
session.sendUpstreamPacket(blockPacket);
|
||||
|
||||
tag = NbtMap.builder()
|
||||
.putString("id", "Chest")
|
||||
.putInt("x", pairPosition.getX())
|
||||
.putInt("y", pairPosition.getY())
|
||||
.putInt("z", pairPosition.getZ())
|
||||
.putInt("pairx", position.getX())
|
||||
.putInt("pairz", position.getZ())
|
||||
.putString("CustomName", inventory.getTitle())
|
||||
.putBoolean("pairlead", true);
|
||||
|
||||
dataPacket = new BlockEntityDataPacket();
|
||||
dataPacket.setData(tag.build());
|
||||
dataPacket.setBlockPosition(pairPosition);
|
||||
session.sendUpstreamPacket(dataPacket);
|
||||
|
||||
inventory.setHolderPosition(position);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openInventory(GeyserSession session, Inventory inventory) {
|
||||
ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket();
|
||||
containerOpenPacket.setId((byte) inventory.getBedrockId());
|
||||
containerOpenPacket.setType(ContainerType.CONTAINER);
|
||||
containerOpenPacket.setBlockPosition(inventory.getHolderPosition());
|
||||
containerOpenPacket.setUniqueEntityId(inventory.getHolderId());
|
||||
session.sendUpstreamPacket(containerOpenPacket);
|
||||
|
||||
GeyserImpl.getInstance().getLogger().debug(session, containerOpenPacket.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeInventory(GeyserSession session, Inventory inventory) {
|
||||
// this should no longer be possible; as we're storing the translator with the inventory to avoid desyncs.
|
||||
// TODO use generics to ensure we don't need to cast unsafely in the first place
|
||||
if (!(inventory instanceof Container container)) {
|
||||
GeyserImpl.getInstance().getLogger().warning("Tried to close a non-container inventory in a block inventory holder! Please report this error on discord.");
|
||||
GeyserImpl.getInstance().getLogger().warning("Current inventory translator: " + InventoryUtils.getInventoryTranslator(session).getClass().getSimpleName());
|
||||
GeyserImpl.getInstance().getLogger().warning("Current inventory: " + inventory.getClass().getSimpleName());
|
||||
// Try to save ourselves? maybe?
|
||||
// https://github.com/GeyserMC/Geyser/issues/4141
|
||||
// TODO: improve once this issue is pinned down
|
||||
if (session.getOpenInventory() != null) {
|
||||
session.getOpenInventory().getTranslator().closeInventory(session, inventory);
|
||||
session.setOpenInventory(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// No need to reset a block since we didn't change any blocks
|
||||
// But send a container close packet because we aren't destroying the original.
|
||||
if (container.isDisplayed()) {
|
||||
ContainerClosePacket packet = new ContainerClosePacket();
|
||||
packet.setId((byte) inventory.getBedrockId());
|
||||
packet.setServerInitiated(true);
|
||||
packet.setType(ContainerType.CONTAINER);
|
||||
session.sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
if (!container.isUsingRealBlock()) {
|
||||
Vector3i holderPos = inventory.getHolderPosition();
|
||||
int realBlock = session.getGeyser().getWorldManager().getBlockAt(session, holderPos);
|
||||
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
|
||||
blockPacket.setDataLayer(0);
|
||||
blockPacket.setBlockPosition(holderPos);
|
||||
blockPacket.setDefinition(session.getBlockMappings().getBedrockBlock(realBlock));
|
||||
session.sendUpstreamPacket(blockPacket);
|
||||
|
||||
holderPos = holderPos.add(Vector3i.UNIT_X);
|
||||
realBlock = session.getGeyser().getWorldManager().getBlockAt(session, holderPos);
|
||||
blockPacket = new UpdateBlockPacket();
|
||||
blockPacket.setDataLayer(0);
|
||||
blockPacket.setBlockPosition(holderPos);
|
||||
blockPacket.setDefinition(session.getBlockMappings().getBedrockBlock(realBlock));
|
||||
session.sendUpstreamPacket(blockPacket);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canUseRealBlock(GeyserSession session, Inventory inventory) {
|
||||
// See BlockInventoryHolder - same concept there except we're also dealing with a specific block state
|
||||
if (session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition())) {
|
||||
BlockState state = session.getGeyser().getWorldManager().blockAt(session, session.getLastInteractionBlockPosition());
|
||||
@@ -86,105 +235,6 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector3i position = InventoryUtils.findAvailableWorldSpace(session);
|
||||
if (position == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3i pairPosition = position.add(Vector3i.UNIT_X);
|
||||
BlockDefinition definition = session.getBlockMappings().getVanillaBedrockBlock(defaultJavaBlockState);
|
||||
|
||||
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
|
||||
blockPacket.setDataLayer(0);
|
||||
blockPacket.setBlockPosition(position);
|
||||
blockPacket.setDefinition(definition);
|
||||
blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
|
||||
session.sendUpstreamPacket(blockPacket);
|
||||
|
||||
NbtMap tag = BlockEntityTranslator.getConstantBedrockTag("Chest", position)
|
||||
.putInt("pairx", pairPosition.getX())
|
||||
.putInt("pairz", pairPosition.getZ())
|
||||
.putString("CustomName", inventory.getTitle()).build();
|
||||
BlockEntityDataPacket dataPacket = new BlockEntityDataPacket();
|
||||
dataPacket.setData(tag);
|
||||
dataPacket.setBlockPosition(position);
|
||||
session.sendUpstreamPacket(dataPacket);
|
||||
|
||||
blockPacket = new UpdateBlockPacket();
|
||||
blockPacket.setDataLayer(0);
|
||||
blockPacket.setBlockPosition(pairPosition);
|
||||
blockPacket.setDefinition(definition);
|
||||
blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY);
|
||||
session.sendUpstreamPacket(blockPacket);
|
||||
|
||||
tag = NbtMap.builder()
|
||||
.putString("id", "Chest")
|
||||
.putInt("x", pairPosition.getX())
|
||||
.putInt("y", pairPosition.getY())
|
||||
.putInt("z", pairPosition.getZ())
|
||||
.putInt("pairx", position.getX())
|
||||
.putInt("pairz", position.getZ())
|
||||
.putString("CustomName", inventory.getTitle()).build();
|
||||
dataPacket = new BlockEntityDataPacket();
|
||||
dataPacket.setData(tag);
|
||||
dataPacket.setBlockPosition(pairPosition);
|
||||
session.sendUpstreamPacket(dataPacket);
|
||||
|
||||
inventory.setHolderPosition(position);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openInventory(GeyserSession session, Inventory inventory) {
|
||||
ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket();
|
||||
containerOpenPacket.setId((byte) inventory.getBedrockId());
|
||||
containerOpenPacket.setType(ContainerType.CONTAINER);
|
||||
containerOpenPacket.setBlockPosition(inventory.getHolderPosition());
|
||||
containerOpenPacket.setUniqueEntityId(inventory.getHolderId());
|
||||
session.sendUpstreamPacket(containerOpenPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeInventory(GeyserSession session, Inventory inventory) {
|
||||
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: " + session.getInventoryTranslator().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;
|
||||
}
|
||||
|
||||
if (container.isUsingRealBlock()) {
|
||||
// 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(ContainerType.CONTAINER);
|
||||
session.sendUpstreamPacket(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3i holderPos = inventory.getHolderPosition();
|
||||
int realBlock = session.getGeyser().getWorldManager().getBlockAt(session, holderPos);
|
||||
UpdateBlockPacket blockPacket = new UpdateBlockPacket();
|
||||
blockPacket.setDataLayer(0);
|
||||
blockPacket.setBlockPosition(holderPos);
|
||||
blockPacket.setDefinition(session.getBlockMappings().getBedrockBlock(realBlock));
|
||||
session.sendUpstreamPacket(blockPacket);
|
||||
|
||||
holderPos = holderPos.add(Vector3i.UNIT_X);
|
||||
realBlock = session.getGeyser().getWorldManager().getBlockAt(session, holderPos);
|
||||
blockPacket = new UpdateBlockPacket();
|
||||
blockPacket.setDataLayer(0);
|
||||
blockPacket.setBlockPosition(holderPos);
|
||||
blockPacket.setDefinition(session.getBlockMappings().getBedrockBlock(realBlock));
|
||||
session.sendUpstreamPacket(blockPacket);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
package org.geysermc.geyser.translator.inventory.chest;
|
||||
|
||||
import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType;
|
||||
import org.geysermc.geyser.inventory.Container;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.holder.BlockInventoryHolder;
|
||||
import org.geysermc.geyser.inventory.holder.InventoryHolder;
|
||||
@@ -57,16 +58,16 @@ public class SingleChestInventoryTranslator extends ChestInventoryTranslator {
|
||||
|
||||
@Override
|
||||
public boolean prepareInventory(GeyserSession session, Inventory inventory) {
|
||||
return holder.prepareInventory(this, session, inventory);
|
||||
return holder.prepareInventory(session, (Container) inventory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openInventory(GeyserSession session, Inventory inventory) {
|
||||
holder.openInventory(this, session, inventory);
|
||||
holder.openInventory(session, (Container) inventory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeInventory(GeyserSession session, Inventory inventory) {
|
||||
holder.closeInventory(this, session, inventory, ContainerType.CONTAINER);
|
||||
holder.closeInventory(session, (Container) inventory, ContainerType.CONTAINER);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,12 +25,6 @@
|
||||
|
||||
package org.geysermc.geyser.translator.protocol.bedrock;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Filterable;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.WritableBookContent;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundEditBookPacket;
|
||||
import org.cloudburstmc.protocol.bedrock.packet.BookEditPacket;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.item.type.WrittenBookItem;
|
||||
@@ -38,8 +32,17 @@ import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponents;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.Filterable;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.component.WritableBookContent;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundEditBookPacket;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Translator(packet = BookEditPacket.class)
|
||||
public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket> {
|
||||
@@ -129,7 +132,7 @@ public class BedrockBookEditTranslator extends PacketTranslator<BookEditPacket>
|
||||
|
||||
// Update local copy
|
||||
session.getPlayerInventory().setItem(36 + session.getPlayerInventory().getHeldItemSlot(), GeyserItemStack.from(bookItem), session);
|
||||
session.getInventoryTranslator().updateInventory(session, session.getPlayerInventory());
|
||||
session.getPlayerInventory().updateInventory();
|
||||
|
||||
String title;
|
||||
if (packet.getAction() == BookEditPacket.Action.SIGN_BOOK) {
|
||||
|
||||
@@ -26,38 +26,73 @@
|
||||
package org.geysermc.geyser.translator.protocol.bedrock;
|
||||
|
||||
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.MerchantContainer;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.MerchantInventoryTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.translator.protocol.java.inventory.JavaMerchantOffersTranslator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.geysermc.geyser.util.InventoryUtils.MAGIC_VIRTUAL_INVENTORY_HACK;
|
||||
|
||||
@Translator(packet = ContainerClosePacket.class)
|
||||
public class BedrockContainerCloseTranslator extends PacketTranslator<ContainerClosePacket> {
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ContainerClosePacket packet) {
|
||||
GeyserImpl.getInstance().getLogger().debug(session, packet.toString());
|
||||
byte bedrockId = packet.getId();
|
||||
|
||||
//Client wants close confirmation
|
||||
session.sendUpstreamPacket(packet);
|
||||
session.setClosingInventory(false);
|
||||
|
||||
if (bedrockId == -1 && session.getOpenInventory() instanceof MerchantContainer) {
|
||||
// 1.16.200 - window ID is always -1 sent from Bedrock
|
||||
bedrockId = (byte) session.getOpenInventory().getBedrockId();
|
||||
// 1.21.70: Bedrock can reject opening inventories - in those cases it replies with -1
|
||||
Inventory openInventory = session.getOpenInventory();
|
||||
if (bedrockId == -1 && openInventory != 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 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);
|
||||
|
||||
session.scheduleInEventLoop(() -> {
|
||||
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
|
||||
latencyPacket.setFromServer(true);
|
||||
latencyPacket.setTimestamp(MAGIC_VIRTUAL_INVENTORY_HACK);
|
||||
session.sendUpstreamPacket(latencyPacket);
|
||||
GeyserImpl.getInstance().getLogger().debug(session, "Unable to open a virtual inventory, sent another latency packet!");
|
||||
}, 100, TimeUnit.MILLISECONDS);
|
||||
return;
|
||||
} else {
|
||||
GeyserImpl.getInstance().getLogger().debug(session, "Exceeded 3 attempts to open a virtual inventory!");
|
||||
GeyserImpl.getInstance().getLogger().debug(session, packet + " " + session.getOpenInventory().getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Inventory openInventory = session.getOpenInventory();
|
||||
session.setPendingOrCurrentBedrockInventoryId(-1);
|
||||
session.setContainerOpenAttempts(0);
|
||||
closeCurrentOrOpenPending(session, bedrockId, openInventory);
|
||||
}
|
||||
|
||||
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);
|
||||
openInventory.setPending(false);
|
||||
|
||||
if (openInventory instanceof MerchantContainer merchantContainer && merchantContainer.getPendingOffersPacket() != null) {
|
||||
JavaMerchantOffersTranslator.openMerchant(session, merchantContainer.getPendingOffersPacket(), merchantContainer);
|
||||
|
||||
@@ -48,7 +48,7 @@ public class BedrockFilterTextTranslator extends PacketTranslator<FilterTextPack
|
||||
packet.setFromServer(true);
|
||||
if (session.getOpenInventory() instanceof AnvilContainer anvilContainer) {
|
||||
packet.setText(anvilContainer.checkForRename(session, packet.getText()));
|
||||
session.getInventoryTranslator().updateSlot(session, anvilContainer, 1);
|
||||
anvilContainer.updateSlot(1);
|
||||
}
|
||||
session.sendUpstreamPacket(packet);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
|
||||
/**
|
||||
* The packet sent for server-authoritative-style inventory transactions.
|
||||
@@ -44,7 +45,7 @@ public class BedrockItemStackRequestTranslator extends PacketTranslator<ItemStac
|
||||
if (inventory == null)
|
||||
return;
|
||||
|
||||
InventoryTranslator translator = session.getInventoryTranslator();
|
||||
InventoryTranslator translator = InventoryUtils.getInventoryTranslator(session);
|
||||
translator.translateRequests(session, inventory, packet.getRequests());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.cloudburstmc.protocol.bedrock.packet.LecternUpdatePacket;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.LecternContainer;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
@@ -62,9 +61,8 @@ 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()) {
|
||||
LecternInventoryTranslator translator = (LecternInventoryTranslator) session.getInventoryTranslator();
|
||||
Inventory inventory = session.getOpenInventory();
|
||||
translator.updateProperty(session, inventory, 0, newJavaPage);
|
||||
inventory.getTranslator().updateProperty(session, inventory, 0, newJavaPage);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -73,12 +71,12 @@ public class BedrockLecternUpdateTranslator extends PacketTranslator<LecternUpda
|
||||
// is a byte when transmitted over the network and therefore this stops us at 128
|
||||
if (newJavaPage > currentJavaPage) {
|
||||
for (int i = currentJavaPage; i < newJavaPage; i++) {
|
||||
ServerboundContainerButtonClickPacket clickButtonPacket = new ServerboundContainerButtonClickPacket(session.getOpenInventory().getJavaId(), 2);
|
||||
ServerboundContainerButtonClickPacket clickButtonPacket = new ServerboundContainerButtonClickPacket(lecternContainer.getJavaId(), 2);
|
||||
session.sendDownstreamGamePacket(clickButtonPacket);
|
||||
}
|
||||
} else {
|
||||
for (int i = currentJavaPage; i > newJavaPage; i--) {
|
||||
ServerboundContainerButtonClickPacket clickButtonPacket = new ServerboundContainerButtonClickPacket(session.getOpenInventory().getJavaId(), 1);
|
||||
ServerboundContainerButtonClickPacket clickButtonPacket = new ServerboundContainerButtonClickPacket(lecternContainer.getJavaId(), 1);
|
||||
session.sendDownstreamGamePacket(clickButtonPacket);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import org.geysermc.geyser.entity.attribute.GeyserAttributeType;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.common.serverbound.ServerboundKeepAlivePacket;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -64,6 +65,9 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<Netwo
|
||||
return;
|
||||
}
|
||||
|
||||
if (session.getPendingOrCurrentBedrockInventoryId() != -1) {
|
||||
InventoryUtils.openPendingInventory(session);
|
||||
} else {
|
||||
session.scheduleInEventLoop(() -> {
|
||||
// Hack to fix the url image loading bug
|
||||
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
|
||||
@@ -79,6 +83,7 @@ public class BedrockNetworkStackLatencyTranslator extends PacketTranslator<Netwo
|
||||
session.sendUpstreamPacket(attributesPacket);
|
||||
}, 500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldExecuteInEventLoop() {
|
||||
|
||||
@@ -65,8 +65,8 @@ public class BedrockSetLocalPlayerAsInitializedTranslator extends PacketTranslat
|
||||
session.getEntityCache().updateBossBars();
|
||||
|
||||
// Double sigh - https://github.com/GeyserMC/Geyser/issues/2677 - as of Bedrock 1.18
|
||||
if (session.getOpenInventory() != null && session.getOpenInventory().isPending()) {
|
||||
InventoryUtils.openInventory(session, session.getOpenInventory());
|
||||
if (session.getOpenInventory() != null) {
|
||||
InventoryUtils.openPendingInventory(session);
|
||||
}
|
||||
|
||||
// What am I to expect - as of Bedrock 1.18
|
||||
|
||||
@@ -31,7 +31,6 @@ import org.geysermc.geyser.entity.type.player.SessionPlayerEntity;
|
||||
import org.geysermc.geyser.level.JavaDimension;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.session.cache.registry.JavaRegistries;
|
||||
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.ChunkUtils;
|
||||
@@ -62,7 +61,6 @@ public class JavaRespawnTranslator extends PacketTranslator<ClientboundRespawnPa
|
||||
entity.setHealth(entity.getMaxHealth());
|
||||
entity.getAttributes().put(GeyserAttributeType.HEALTH, entity.createHealthAttribute());
|
||||
|
||||
session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
|
||||
session.setOpenInventory(null);
|
||||
session.setClosingInventory(false);
|
||||
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
package org.geysermc.geyser.translator.protocol.java.inventory;
|
||||
|
||||
import org.geysermc.geyser.inventory.PlayerInventory;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundContainerClosePacket;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundContainerClosePacket;
|
||||
|
||||
@Translator(packet = ClientboundContainerClosePacket.class)
|
||||
public class JavaContainerCloseTranslator extends PacketTranslator<ClientboundContainerClosePacket> {
|
||||
|
||||
@@ -79,7 +79,7 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
|
||||
session.getPlayerInventory().setCursor(cursor, session);
|
||||
InventoryUtils.updateCursor(session);
|
||||
|
||||
if (session.getInventoryTranslator() instanceof SmithingInventoryTranslator) {
|
||||
if (InventoryUtils.getInventoryTranslator(session) 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 -
|
||||
@@ -92,11 +92,11 @@ public class JavaContainerSetContentTranslator extends PacketTranslator<Clientbo
|
||||
}
|
||||
|
||||
private void updateInventory(GeyserSession session, Inventory inventory, int containerId) {
|
||||
InventoryTranslator translator = session.getInventoryTranslator();
|
||||
InventoryTranslator translator = InventoryUtils.getInventoryTranslator(session);
|
||||
if (containerId == 0 && !(translator instanceof PlayerInventoryTranslator)) {
|
||||
// In rare cases, the window ID can still be 0 but Java treats it as valid
|
||||
InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR.updateInventory(session, inventory);
|
||||
} else if (translator != null) {
|
||||
} else {
|
||||
translator.updateInventory(session, inventory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ package org.geysermc.geyser.translator.protocol.java.inventory;
|
||||
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
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;
|
||||
@@ -42,9 +41,6 @@ public class JavaContainerSetDataTranslator extends PacketTranslator<Clientbound
|
||||
if (inventory == null)
|
||||
return;
|
||||
|
||||
InventoryTranslator translator = session.getInventoryTranslator();
|
||||
if (translator != null) {
|
||||
translator.updateProperty(session, inventory, packet.getRawProperty(), packet.getValue());
|
||||
}
|
||||
inventory.updateProperty(packet.getRawProperty(), packet.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ public class JavaContainerSetSlotTranslator extends PacketTranslator<Clientbound
|
||||
return;
|
||||
}
|
||||
|
||||
InventoryTranslator translator = session.getInventoryTranslator();
|
||||
InventoryTranslator translator = InventoryUtils.getInventoryTranslator(session);
|
||||
if (translator != null) {
|
||||
int slot = packet.getSlot();
|
||||
if (slot >= inventory.getSize()) {
|
||||
@@ -285,11 +285,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(session.getInventoryTranslator().javaSlotToBedrock(SmithingInventoryTranslator.MATERIAL));
|
||||
slotPacket.setSlot(InventoryUtils.getInventoryTranslator(session).javaSlotToBedrock(SmithingInventoryTranslator.MATERIAL));
|
||||
slotPacket.setItem(ItemData.AIR);
|
||||
session.sendUpstreamPacket(slotPacket);
|
||||
|
||||
session.getInventoryTranslator().updateSlot(session, inventory, SmithingInventoryTranslator.MATERIAL);
|
||||
InventoryUtils.getInventoryTranslator(session).updateSlot(session, inventory, SmithingInventoryTranslator.MATERIAL);
|
||||
}, 150, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,6 @@
|
||||
|
||||
package org.geysermc.geyser.translator.protocol.java.inventory;
|
||||
|
||||
import org.geysermc.geyser.entity.type.living.animal.horse.SkeletonHorseEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.horse.ZombieHorseEntity;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundHorseScreenOpenPacket;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtMapBuilder;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
@@ -38,6 +35,8 @@ import org.geysermc.geyser.entity.type.Entity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.horse.CamelEntity;
|
||||
import org.geysermc.geyser.entity.type.living.animal.horse.ChestedHorseEntity;
|
||||
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.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
@@ -47,6 +46,7 @@ import org.geysermc.geyser.translator.inventory.horse.LlamaInventoryTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundHorseScreenOpenPacket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -153,7 +153,6 @@ public class JavaHorseScreenOpenTranslator extends PacketTranslator<ClientboundH
|
||||
updateEquipPacket.setTag(builder.build());
|
||||
session.sendUpstreamPacket(updateEquipPacket);
|
||||
|
||||
session.setInventoryTranslator(inventoryTranslator);
|
||||
InventoryUtils.openInventory(session, new Container(entity.getNametag(), packet.getContainerId(), slotCount, null, session.getPlayerInventory()));
|
||||
InventoryUtils.openInventory(session, new Container(session, entity.getNametag(), packet.getContainerId(), slotCount, null, session.getPlayerInventory(), inventoryTranslator));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,17 +63,15 @@ public class JavaOpenBookTranslator extends PacketTranslator<ClientboundOpenBook
|
||||
Inventory openInventory = session.getOpenInventory();
|
||||
if (openInventory != null) {
|
||||
InventoryUtils.closeInventory(session, openInventory.getJavaId(), true);
|
||||
|
||||
InventoryUtils.sendJavaContainerClose(session, openInventory);
|
||||
}
|
||||
|
||||
InventoryTranslator translator = InventoryTranslator.inventoryTranslator(ContainerType.LECTERN);
|
||||
Objects.requireNonNull(translator, "could not find lectern inventory translator!");
|
||||
session.setInventoryTranslator(translator);
|
||||
|
||||
// Should never be null
|
||||
Objects.requireNonNull(translator, "lectern translator must exist");
|
||||
Inventory inventory = translator.createInventory("", FAKE_LECTERN_WINDOW_ID, ContainerType.LECTERN, session.getPlayerInventory());
|
||||
Inventory inventory = translator.createInventory(session, "", FAKE_LECTERN_WINDOW_ID, ContainerType.LECTERN, session.getPlayerInventory());
|
||||
((LecternContainer) inventory).setFakeLecternBook(stack, session);
|
||||
InventoryUtils.openInventory(session, inventory);
|
||||
}
|
||||
|
||||
@@ -25,10 +25,8 @@
|
||||
|
||||
package org.geysermc.geyser.translator.protocol.java.inventory;
|
||||
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundOpenScreenPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClosePacket;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.session.GeyserSession;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
@@ -37,6 +35,9 @@ import org.geysermc.geyser.translator.protocol.PacketTranslator;
|
||||
import org.geysermc.geyser.translator.protocol.Translator;
|
||||
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||
import org.geysermc.geyser.util.InventoryUtils;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.inventory.ClientboundOpenScreenPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.inventory.ServerboundContainerClosePacket;
|
||||
|
||||
@Translator(packet = ClientboundOpenScreenPacket.class)
|
||||
public class JavaOpenScreenTranslator extends PacketTranslator<ClientboundOpenScreenPacket> {
|
||||
@@ -45,6 +46,7 @@ public class JavaOpenScreenTranslator extends PacketTranslator<ClientboundOpenSc
|
||||
|
||||
@Override
|
||||
public void translate(GeyserSession session, ClientboundOpenScreenPacket packet) {
|
||||
GeyserImpl.getInstance().getLogger().debug(session, packet.toString());
|
||||
if (packet.getContainerId() == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -65,6 +67,7 @@ public class JavaOpenScreenTranslator extends PacketTranslator<ClientboundOpenSc
|
||||
if (openInventory != null) {
|
||||
InventoryUtils.closeInventory(session, openInventory.getJavaId(), true);
|
||||
}
|
||||
|
||||
ServerboundContainerClosePacket closeWindowPacket = new ServerboundContainerClosePacket(packet.getContainerId());
|
||||
session.sendDownstreamGamePacket(closeWindowPacket);
|
||||
return;
|
||||
@@ -72,18 +75,35 @@ public class JavaOpenScreenTranslator extends PacketTranslator<ClientboundOpenSc
|
||||
|
||||
String name = MessageTranslator.convertMessage(packet.getTitle(), session.locale());
|
||||
|
||||
Inventory newInventory = newTranslator.createInventory(name, packet.getContainerId(), packet.getType(), session.getPlayerInventory());
|
||||
Inventory newInventory = newTranslator.createInventory(session, name, packet.getContainerId(), packet.getType(), session.getPlayerInventory());
|
||||
if (openInventory != null) {
|
||||
// If the window type is the same, don't close.
|
||||
// In rare cases, inventories can do funny things where it keeps the same window type up but change the contents.
|
||||
// Or, inventory names can change (useful for JsonUI). In these cases, we need to close the old inventory.
|
||||
if (openInventory.getContainerType() != packet.getType() || !openInventory.getTitle().equals(name)) {
|
||||
// Sometimes the server can double-open an inventory with the same ID - don't confirm in that instance.
|
||||
InventoryUtils.closeInventory(session, openInventory.getJavaId(), openInventory.getJavaId() != packet.getContainerId());
|
||||
}
|
||||
// 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 the current inventory is still pending, it'll be updated once open
|
||||
if (newInventory.isDisplayed()) {
|
||||
newTranslator.updateInventory(session, newInventory);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
InventoryUtils.closeInventory(session, openInventory.getJavaId(), true);
|
||||
}
|
||||
|
||||
session.setInventoryTranslator(newTranslator);
|
||||
InventoryUtils.openInventory(session, newInventory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
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;
|
||||
@@ -34,8 +35,8 @@ import org.cloudburstmc.protocol.bedrock.data.definitions.ItemDefinition;
|
||||
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.cloudburstmc.protocol.bedrock.packet.NetworkStackLatencyPacket;
|
||||
import org.geysermc.geyser.GeyserImpl;
|
||||
import org.geysermc.geyser.inventory.Container;
|
||||
import org.geysermc.geyser.inventory.GeyserItemStack;
|
||||
import org.geysermc.geyser.inventory.Inventory;
|
||||
import org.geysermc.geyser.inventory.LecternContainer;
|
||||
@@ -55,7 +56,6 @@ import org.geysermc.geyser.text.ChatColor;
|
||||
import org.geysermc.geyser.text.GeyserLocale;
|
||||
import org.geysermc.geyser.translator.inventory.InventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.LecternInventoryTranslator;
|
||||
import org.geysermc.geyser.translator.inventory.chest.DoubleChestInventoryTranslator;
|
||||
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;
|
||||
@@ -73,7 +73,6 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public class InventoryUtils {
|
||||
@@ -85,53 +84,119 @@ public class InventoryUtils {
|
||||
|
||||
public static final ItemStack REFRESH_ITEM = new ItemStack(1, 127, new DataComponents(new HashMap<>()));
|
||||
|
||||
/**
|
||||
* An arbitrary, negative long used to delay the opening of virtual inventories until the client is
|
||||
* likely ready for it. The {@link org.geysermc.geyser.translator.protocol.bedrock.BedrockNetworkStackLatencyTranslator}
|
||||
* will then call {@link #openPendingInventory(GeyserSession)}, which would finish opening the inventory.
|
||||
*/
|
||||
public static final long MAGIC_VIRTUAL_INVENTORY_HACK = -9876543210L;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public static void openInventory(GeyserSession session, Inventory inventory) {
|
||||
session.setOpenInventory(inventory);
|
||||
if (session.isClosingInventory() || !session.getUpstream().isInitialized()) {
|
||||
if (session.isClosingInventory() || !session.getUpstream().isInitialized() || session.getPendingOrCurrentBedrockInventoryId() != -1) {
|
||||
// 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());
|
||||
return;
|
||||
}
|
||||
displayInventory(session, inventory);
|
||||
}
|
||||
|
||||
public static void displayInventory(GeyserSession session, Inventory inventory) {
|
||||
InventoryTranslator translator = session.getInventoryTranslator();
|
||||
if (translator.prepareInventory(session, inventory)) {
|
||||
if (translator instanceof DoubleChestInventoryTranslator && !((Container) inventory).isUsingRealBlock()) {
|
||||
session.scheduleInEventLoop(() -> {
|
||||
Inventory openInv = session.getOpenInventory();
|
||||
if (openInv != null && openInv.getJavaId() == inventory.getJavaId()) {
|
||||
translator.openInventory(session, inventory);
|
||||
translator.updateInventory(session, inventory);
|
||||
openInv.setDisplayed(true);
|
||||
} else if (openInv != null && openInv.isPending()) {
|
||||
// Presumably, this inventory is no longer relevant, and the client doesn't care about it
|
||||
displayInventory(session, openInv);
|
||||
/**
|
||||
* Called when the Bedrock client is ready to open a pending inventory.
|
||||
* Due to the nature of possible changes in the delayed time, this method also re-checks for changes that might have
|
||||
* 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()) {
|
||||
session.setPendingOrCurrentBedrockInventoryId(-1);
|
||||
GeyserImpl.getInstance().getLogger().debug(session, "No pending inventory, not opening an inventory! Current inventory: %s", debugInventory(currentInventory));
|
||||
return;
|
||||
}
|
||||
}, 200, TimeUnit.MILLISECONDS);
|
||||
|
||||
// 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);
|
||||
return;
|
||||
}
|
||||
|
||||
GeyserImpl.getInstance().getLogger().debug(session, "Opening any pending inventory! " + debugInventory(currentInventory));
|
||||
displayInventory(session, currentInventory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares and displays the current inventory. If necessary, it will queue the opening of virtual inventories.
|
||||
* @param inventory the inventory to display
|
||||
*/
|
||||
public static void displayInventory(GeyserSession session, Inventory inventory) {
|
||||
InventoryTranslator translator = inventory.getTranslator();
|
||||
if (translator.prepareInventory(session, inventory)) {
|
||||
session.setPendingOrCurrentBedrockInventoryId(inventory.getBedrockId());
|
||||
if (translator.requiresOpeningDelay(session, inventory)) {
|
||||
inventory.setPending(true);
|
||||
|
||||
NetworkStackLatencyPacket latencyPacket = new NetworkStackLatencyPacket();
|
||||
latencyPacket.setFromServer(true);
|
||||
latencyPacket.setTimestamp(MAGIC_VIRTUAL_INVENTORY_HACK);
|
||||
session.sendUpstreamPacket(latencyPacket);
|
||||
|
||||
GeyserImpl.getInstance().getLogger().debug(session, "Queuing virtual inventory (%s)", debugInventory(inventory));
|
||||
} else {
|
||||
translator.openInventory(session, inventory);
|
||||
translator.updateInventory(session, inventory);
|
||||
inventory.setDisplayed(true);
|
||||
openAndUpdateInventory(session, inventory);
|
||||
}
|
||||
} 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);
|
||||
session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens and updates an inventory, and resets no longer used inventory variables.
|
||||
*/
|
||||
public static void openAndUpdateInventory(GeyserSession session, Inventory inventory) {
|
||||
inventory.getTranslator().openInventory(session, inventory);
|
||||
inventory.getTranslator().updateInventory(session, inventory);
|
||||
inventory.setDisplayed(true);
|
||||
inventory.setPending(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current inventory translator.
|
||||
*/
|
||||
public static @NonNull InventoryTranslator getInventoryTranslator(GeyserSession session) {
|
||||
Inventory inventory = session.getOpenInventory();
|
||||
if (inventory == null) {
|
||||
return InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR;
|
||||
}
|
||||
return inventory.getTranslator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the inventory that matches the java id.
|
||||
* @param session the session to close the inventory for
|
||||
* @param javaId the id of the inventory to close
|
||||
* @param confirm whether to wait for the session to process the close before opening a new inventory.
|
||||
*/
|
||||
public static void closeInventory(GeyserSession session, int javaId, boolean confirm) {
|
||||
session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session);
|
||||
updateCursor(session);
|
||||
|
||||
Inventory inventory = getInventory(session, javaId);
|
||||
if (inventory != null) {
|
||||
InventoryTranslator translator = session.getInventoryTranslator();
|
||||
InventoryTranslator translator = inventory.getTranslator();
|
||||
translator.closeInventory(session, inventory);
|
||||
if (confirm && inventory.isDisplayed() && !inventory.isPending()
|
||||
&& !(translator instanceof LecternInventoryTranslator) // Closing lecterns is not followed with a close confirmation
|
||||
@@ -139,8 +204,9 @@ public class InventoryUtils {
|
||||
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.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR);
|
||||
|
||||
session.setOpenInventory(null);
|
||||
}
|
||||
|
||||
@@ -421,4 +487,18 @@ public class InventoryUtils {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static String debugInventory(@Nullable Inventory inventory) {
|
||||
if (inventory == null) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ org.gradle.vfs.watch=false
|
||||
|
||||
group=org.geysermc
|
||||
id=geyser
|
||||
version=2.6.2-SNAPSHOT
|
||||
version=2.7.0-SNAPSHOT
|
||||
description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers.
|
||||
|
||||
Reference in New Issue
Block a user