From 7f4b588cdfe16df614fc1ce5f6b723589965809e Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Fri, 16 Oct 2020 15:25:05 -0800 Subject: [PATCH 001/161] server inventory. WORK IN PROGRESS wip commit of implementing server authoritative inventories. there is a lot of experimental and debug code. this is NOT ready for testing or review. --- .../org/geysermc/connector/entity/Entity.java | 8 +- .../connector/inventory/Container.java | 69 +++ .../inventory/EnchantmentInventory.java | 40 ++ .../connector/inventory/FurnaceInventory.java | 41 ++ .../connector/inventory/GeyserItemStack.java | 123 +++++ .../connector/inventory/Inventory.java | 30 +- .../inventory/MerchantContainer.java | 44 ++ .../connector/inventory/PlayerInventory.java | 22 +- .../network/session/GeyserSession.java | 81 ++- .../network/session/cache/InventoryCache.java | 61 --- .../BedrockContainerCloseTranslator.java | 32 +- ...BedrockInventoryTransactionTranslator.java | 45 +- .../BedrockItemStackRequestTranslator.java | 49 ++ .../BedrockMobEquipmentTranslator.java | 4 +- .../entity/BedrockEntityEventTranslator.java | 32 +- .../player/BedrockInteractTranslator.java | 7 +- .../inventory/AnvilInventoryTranslator.java | 167 ------- .../inventory/BaseInventoryTranslator.java | 50 +- .../inventory/BedrockContainerSlot.java | 36 ++ .../inventory/BlockInventoryTranslator.java | 72 --- .../inventory/BrewingInventoryTranslator.java | 99 ---- .../inventory/ChestInventoryTranslator.java | 20 +- .../CraftingInventoryTranslator.java | 89 ---- .../EnchantmentInventoryTranslator.java | 267 ---------- .../inventory/FurnaceInventoryTranslator.java | 70 --- .../GrindstoneInventoryTranslator.java | 69 --- .../inventory/InventoryTranslator.java | 460 +++++++++++++++++- .../MerchantInventoryTranslator.java | 92 ++-- .../inventory/PlayerInventoryTranslator.java | 115 ++--- .../SmithingInventoryTranslator.java | 69 --- .../inventory/action/ClickPlan.java | 125 ----- .../action/InventoryActionDataTranslator.java | 338 ------------- .../inventory/{action => click}/Click.java | 17 +- .../inventory/click/ClickPlan.java | 211 ++++++++ .../updater/ChestInventoryUpdater.java | 4 +- .../updater/ContainerInventoryUpdater.java | 4 +- .../inventory/updater/InventoryUpdater.java | 4 +- ...ryUpdater.java => UIInventoryUpdater.java} | 7 +- .../java/JavaDeclareRecipesTranslator.java | 11 +- .../java/JavaRespawnTranslator.java | 2 +- .../player/JavaPlayerActionAckTranslator.java | 13 +- .../JavaPlayerChangeHeldItemTranslator.java | 14 +- .../window/JavaCloseWindowTranslator.java | 6 +- .../JavaConfirmTransactionTranslator.java | 11 +- .../java/window/JavaOpenWindowTranslator.java | 77 +-- .../java/window/JavaSetSlotTranslator.java | 54 +- .../window/JavaWindowItemsTranslator.java | 36 +- .../window/JavaWindowPropertyTranslator.java | 17 +- .../world/JavaNotifyClientTranslator.java | 2 +- .../java/world/JavaTradeListTranslator.java | 35 +- .../sound/BlockSoundInteractionHandler.java | 9 +- .../sound/EntitySoundInteractionHandler.java | 9 +- .../block/BucketSoundInteractionHandler.java | 2 +- .../MilkCowSoundInteractionHandler.java | 2 +- .../geysermc/connector/utils/BlockUtils.java | 2 +- .../connector/utils/InventoryUtils.java | 91 ++-- connector/src/main/resources/languages | 2 +- connector/src/main/resources/mappings | 2 +- 58 files changed, 1611 insertions(+), 1859 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/Container.java create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/EnchantmentInventory.java create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/BedrockContainerSlot.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/SmithingInventoryTranslator.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java rename connector/src/main/java/org/geysermc/connector/network/translators/inventory/{action => click}/Click.java (66%) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java rename connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/{CursorInventoryUpdater.java => UIInventoryUpdater.java} (89%) diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 5e825e892..57c827a9f 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -50,6 +50,7 @@ import org.geysermc.connector.entity.attribute.Attribute; import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.living.ArmorStandEntity; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.utils.AttributeUtils; @@ -288,11 +289,12 @@ public class Entity { // Shield code if (session.getPlayerEntity().getEntityId() == entityId && metadata.getFlags().getFlag(EntityFlag.SNEAKING)) { - if ((session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) || - (session.getInventoryCache().getPlayerInventory().getItem(45) != null && session.getInventoryCache().getPlayerInventory().getItem(45).getId() == ItemRegistry.SHIELD.getJavaId())) { + PlayerInventory playerInv = session.getPlayerInventory(); + if ((playerInv.getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) || + (playerInv.getOffhand().getId() == ItemRegistry.SHIELD.getJavaId())) { ClientPlayerUseItemPacket useItemPacket; metadata.getFlags().setFlag(EntityFlag.BLOCKING, true); - if (session.getInventory().getItemInHand() != null && session.getInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) { + if (playerInv.getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) { useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); } // Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Container.java b/connector/src/main/java/org/geysermc/connector/inventory/Container.java new file mode 100644 index 000000000..acf450e14 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/Container.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.inventory; + +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import lombok.Getter; +import lombok.NonNull; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; + +/** + * Combination of {@link Inventory} and {@link PlayerInventory} + */ +@Getter +public class Container extends Inventory { + private final PlayerInventory playerInventory; + private final int containerSize; + + public Container(String title, int id, WindowType windowType, int size, PlayerInventory playerInventory) { + super(title, id, windowType, size); + this.playerInventory = playerInventory; + this.containerSize = this.size + InventoryTranslator.PLAYER_INVENTORY_SIZE; + } + + @Override + public GeyserItemStack getItem(int slot) { + if (slot < this.size) { + return super.getItem(slot); + } else { + return playerInventory.getItem(slot - this.size + InventoryTranslator.PLAYER_INVENTORY_OFFSET); + } + } + + @Override + public void setItem(int slot, @NonNull GeyserItemStack item) { + if (slot < this.size) { + super.setItem(slot, item); + } else { + playerInventory.setItem(slot - this.size + InventoryTranslator.PLAYER_INVENTORY_OFFSET, item); + } + } + + @Override + public int getSize() { + return this.containerSize; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/inventory/EnchantmentInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/EnchantmentInventory.java new file mode 100644 index 000000000..65debc486 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/EnchantmentInventory.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.inventory; + +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData; +import lombok.Getter; + +@Getter +public class EnchantmentInventory extends Inventory { + private EnchantOptionData[] enchantOptions; + + public EnchantmentInventory(String title, int id, WindowType windowType, int size) { + super(title, id, windowType, size); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java new file mode 100644 index 000000000..4dc098d89 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.inventory; + +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import lombok.Getter; +import lombok.Setter; + +@Getter +public class FurnaceInventory extends Inventory { + @Setter + private int test; + + public FurnaceInventory(String title, int id, WindowType windowType, int size) { + super(title, id, windowType, size); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java new file mode 100644 index 000000000..c935fcdb4 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.inventory; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import lombok.Data; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; + +@Data +public class GeyserItemStack { + public static final GeyserItemStack EMPTY = new GeyserItemStack(0, 0, null); + + private final int id; + private int amount; + private CompoundTag nbt; + private int netId; + + public GeyserItemStack(int id) { + this(id, 1); + } + + public GeyserItemStack(int id, int amount) { + this(id, amount, null); + } + + public GeyserItemStack(int id, int amount, CompoundTag nbt) { + this(id, amount, nbt, 1); + } + + public GeyserItemStack(int id, int amount, CompoundTag nbt, int netId) { + this.id = id; + this.amount = amount; + this.nbt = nbt; + this.netId = netId; + } + + public int getId() { + return isEmpty() ? 0 : id; + } + + public int getAmount() { + return isEmpty() ? 0 : amount; + } + + public CompoundTag getNbt() { + return isEmpty() ? null : nbt; + } + + public int getNetId() { + return isEmpty() ? 0 : netId; + } + + public void add(int add) { + amount += add; + } + + public void sub(int sub) { + amount -= sub; + } + + public static GeyserItemStack from(ItemStack itemStack) { + return from(itemStack, 1); + } + + public static GeyserItemStack from(ItemStack itemStack, int netId) { + return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getNbt(), netId); + } + + public ItemStack getItemStack() { + return isEmpty() ? null : new ItemStack(id, amount, nbt); + } + + public ItemData getItemData(GeyserSession session) { + ItemData itemData = ItemTranslator.translateToBedrock(session, getItemStack()); + itemData.setNetId(getNetId()); + return itemData; + } + + public ItemEntry getItemEntry() { + return ItemRegistry.ITEM_ENTRIES.get(getId()); + } + + public boolean isEmpty() { + return amount <= 0 || id == 0; + } + + public GeyserItemStack copy() { + return copy(amount); + } + + public GeyserItemStack copy(int newAmount) { + return isEmpty() ? EMPTY : new GeyserItemStack(id, newAmount, nbt == null ? null : nbt.clone(), netId); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java index 539fe1e26..f4aea4c90 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -25,23 +25,19 @@ package org.geysermc.connector.inventory; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.nukkitx.math.vector.Vector3i; import lombok.Getter; +import lombok.NonNull; import lombok.Setter; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.Arrays; public class Inventory { @Getter protected int id; - @Getter - @Setter - protected boolean open; - @Getter protected WindowType windowType; @@ -52,8 +48,7 @@ public class Inventory { @Setter protected String title; - @Setter - protected ItemStack[] items; + protected GeyserItemStack[] items; @Getter @Setter @@ -64,27 +59,30 @@ public class Inventory { protected long holderId = -1; @Getter - protected AtomicInteger transactionId = new AtomicInteger(1); + protected short transactionId = 0; - public Inventory(int id, WindowType windowType, int size) { + protected Inventory(int id, WindowType windowType, int size) { this("Inventory", id, windowType, size); } - public Inventory(String title, int id, WindowType windowType, int size) { + protected Inventory(String title, int id, WindowType windowType, int size) { this.title = title; this.id = id; this.windowType = windowType; this.size = size; - this.items = new ItemStack[size]; + this.items = new GeyserItemStack[size]; + Arrays.fill(items, GeyserItemStack.EMPTY); } - public ItemStack getItem(int slot) { + public GeyserItemStack getItem(int slot) { return items[slot]; } - public void setItem(int slot, ItemStack item) { - if (item != null && (item.getId() == 0 || item.getAmount() < 1)) - item = null; + public void setItem(int slot, @NonNull GeyserItemStack item) { items[slot] = item; } + + public short getNextTransactionId() { + return ++transactionId; + } } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java new file mode 100644 index 000000000..03ae8ac3a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.inventory; + +import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import lombok.Getter; +import lombok.Setter; +import org.geysermc.connector.entity.Entity; + +@Getter +@Setter +public class MerchantContainer extends Container { + private Entity villager; + private VillagerTrade[] villagerTrades; + + public MerchantContainer(String title, int id, WindowType windowType, int size, PlayerInventory playerInventory) { + super(title, id, windowType, size, playerInventory); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java index 225335a97..e6aeb5cab 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java @@ -25,8 +25,8 @@ package org.geysermc.connector.inventory; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import lombok.Getter; +import lombok.NonNull; import lombok.Setter; public class PlayerInventory extends Inventory { @@ -40,20 +40,24 @@ public class PlayerInventory extends Inventory { private int heldItemSlot; @Getter - private ItemStack cursor; + @Setter + @NonNull + private GeyserItemStack cursor = GeyserItemStack.EMPTY; public PlayerInventory() { super(0, null, 46); heldItemSlot = 0; } - public void setCursor(ItemStack stack) { - if (stack != null && (stack.getId() == 0 || stack.getAmount() < 1)) - stack = null; - cursor = stack; - } - - public ItemStack getItemInHand() { + public GeyserItemStack getItemInHand() { return items[36 + heldItemSlot]; } + + public void setItemInHand(@NonNull GeyserItemStack item) { + items[36 + heldItemSlot] = item; + } + + public GeyserItemStack getOffhand() { + return items[45]; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 1a0bbfb29..1d8120f37 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; import com.github.steveice10.mc.protocol.data.message.MessageSerializer; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; @@ -49,11 +50,14 @@ import com.nukkitx.protocol.bedrock.BedrockServerSession; import com.nukkitx.protocol.bedrock.data.*; import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.packet.*; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; +import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; import org.geysermc.common.window.CustomFormWindow; @@ -63,6 +67,7 @@ import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.PlayerEntity; +import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.auth.AuthData; @@ -71,7 +76,6 @@ import org.geysermc.connector.network.session.cache.*; import org.geysermc.connector.network.translators.BiomeTranslator; import org.geysermc.connector.network.translators.EntityIdentifierRegistry; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; -import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.*; @@ -83,6 +87,12 @@ import java.net.InetSocketAddress; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; import java.util.*; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicInteger; @@ -100,11 +110,20 @@ public class GeyserSession implements CommandSender { private BedrockClientData clientData; private PlayerEntity playerEntity; - private PlayerInventory inventory; + + private final PlayerInventory playerInventory; + @Setter + private Inventory openInventory; + + private final AtomicInteger itemNetId = new AtomicInteger(1); + + @Getter(AccessLevel.NONE) + private final Object inventoryLock = new Object(); + @Getter(AccessLevel.NONE) + private CompletableFuture inventoryFuture; private ChunkCache chunkCache; private EntityCache entityCache; - private InventoryCache inventoryCache; private WorldCache worldCache; private WindowCache windowCache; @Setter @@ -170,9 +189,6 @@ public class GeyserSession implements CommandSender { @Setter private Entity ridingVehicleEntity; - @Setter - private int craftSlot = 0; - @Setter private long lastWindowCloseTime = 0; @@ -187,10 +203,8 @@ public class GeyserSession implements CommandSender { @Setter private long lastInteractedVillagerEid; - /** - * Stores the enchantment information the client has received if they are in an enchantment table GUI - */ - private final EnchantmentInventoryTranslator.EnchantmentSlotData[] enchantmentSlotData = new EnchantmentInventoryTranslator.EnchantmentSlotData[3]; + @Setter + private Int2ObjectMap craftingRecipes; /** * The current attack speed of the player. Used for sending proper cooldown timings. @@ -282,18 +296,19 @@ public class GeyserSession implements CommandSender { this.chunkCache = new ChunkCache(this); this.entityCache = new EntityCache(this); - this.inventoryCache = new InventoryCache(this); this.worldCache = new WorldCache(this); this.windowCache = new WindowCache(this); this.playerEntity = new PlayerEntity(new GameProfile(UUID.randomUUID(), "unknown"), 1, 1, Vector3f.ZERO, Vector3f.ZERO, Vector3f.ZERO); - this.inventory = new PlayerInventory(); + + this.playerInventory = new PlayerInventory(); + this.openInventory = null; + this.inventoryFuture = CompletableFuture.completedFuture(null); + this.craftingRecipes = new Int2ObjectOpenHashMap<>(); this.spawned = false; this.loggedIn = false; - this.inventoryCache.getInventories().put(0, inventory); - bedrockServerSession.addDisconnectHandler(disconnectReason -> { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason)); @@ -529,7 +544,6 @@ public class GeyserSession implements CommandSender { this.chunkCache = null; this.entityCache = null; this.worldCache = null; - this.inventoryCache = null; this.windowCache = null; closed = true; @@ -638,10 +652,47 @@ public class GeyserSession implements CommandSender { startGamePacket.setBlockPalette(BlockTranslator.BLOCKS); startGamePacket.setItemEntries(ItemRegistry.ITEMS); startGamePacket.setVanillaVersion("*"); + // startGamePacket.setMovementServerAuthoritative(true); + startGamePacket.setInventoriesServerAuthoritative(true); startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); upstream.sendPacket(startGamePacket); } + /** + * Adds a new inventory task. + * Inventory tasks are executed one at a time, in order. + * + * @param task the task to run + */ + public void addInventoryTask(Runnable task) { + synchronized (inventoryLock) { + System.out.println("new task " + task.toString()); + inventoryFuture = inventoryFuture.thenRun(task).exceptionally(throwable -> { + GeyserConnector.getInstance().getLogger().error("Error processing inventory task", throwable.getCause()); + return null; + }); + } + } + + /** + * Adds a new inventory task with a delay. + * The delay is achieved by scheduling with the Geyser general thread pool. + * Inventory tasks are executed one at a time, in order. + * + * @param task the delayed task to run + * @param delayMillis delay in milliseconds + */ + public void addInventoryTask(Runnable task, long delayMillis) { + synchronized (inventoryLock) { + System.out.println("new delayed task " + task.toString()); + Executor delayedExecutor = command -> GeyserConnector.getInstance().getGeneralThreadPool().schedule(command, delayMillis, TimeUnit.MILLISECONDS); + inventoryFuture = inventoryFuture.thenRunAsync(task, delayedExecutor).exceptionally(throwable -> { + GeyserConnector.getInstance().getLogger().error("Error processing inventory task", throwable.getCause()); + return null; + }); + } + } + public boolean confirmTeleport(Vector3d position) { if (teleportCache != null) { if (!teleportCache.canConfirm(position)) { diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java deleted file mode 100644 index 032f64024..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/InventoryCache.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.session.cache; - -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import lombok.Getter; -import lombok.Setter; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; - -public class InventoryCache { - - private GeyserSession session; - - @Getter - @Setter - private Inventory openInventory; - - @Getter - private Int2ObjectMap inventories = new Int2ObjectOpenHashMap<>(); - - public InventoryCache(GeyserSession session) { - this.session = session; - } - - public Inventory getPlayerInventory() { - return inventories.get(0); - } - - public void cacheInventory(Inventory inventory) { - inventories.put(inventory.getId(), inventory); - } - - public void uncacheInventory(int id) { - inventories.remove(id); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java index 00905f6d9..5571ff8b9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java @@ -38,24 +38,24 @@ public class BedrockContainerCloseTranslator extends PacketTranslator { + session.setLastWindowCloseTime(0); + byte windowId = packet.getId(); + + if (windowId == -1 && session.getOpenInventory() != null) { + windowId = (byte) session.getOpenInventory().getId(); } - } - if (windowId == 0 || (openInventory != null && openInventory.getId() == windowId)) { - ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId); - session.getDownstream().getSession().send(closeWindowPacket); - InventoryUtils.closeInventory(session, windowId); - } + Inventory openInventory = session.getOpenInventory(); + if (openInventory != null && windowId == openInventory.getId()) { + System.out.println(packet); + ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId); + session.getDownstream().getSession().send(closeWindowPacket); + InventoryUtils.closeInventory(session, windowId); + } - //Client wants close confirmation - session.sendUpstreamPacket(packet); + //Client wants close confirmation + session.sendUpstreamPacket(packet); + }); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index b81025beb..9dd0f1996 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -25,7 +25,6 @@ package org.geysermc.connector.network.translators.bedrock; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; @@ -43,23 +42,23 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; +import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; +import com.nukkitx.protocol.bedrock.data.inventory.InventorySource; import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import org.geysermc.connector.entity.CommandBlockMinecartEntity; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity; -import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.sound.EntitySoundInteractionHandler; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockUtils; -import org.geysermc.connector.utils.InventoryUtils; import java.util.concurrent.TimeUnit; @@ -70,15 +69,36 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { + if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot()) + return; + if (session.getPlayerInventory().getItemInHand().isEmpty()) + return; + + boolean dropAll = worldAction.getToItem().getCount() > 1; + ClientPlayerActionPacket dropAllPacket = new ClientPlayerActionPacket( + dropAll ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM, + new Position(0, 0, 0), + BlockFace.DOWN + ); + session.sendDownstreamPacket(dropAllPacket); + + if (dropAll) { + session.getPlayerInventory().setItemInHand(GeyserItemStack.EMPTY); + } else { + session.getPlayerInventory().getItemInHand().sub(1); + } + }); + } + } break; case INVENTORY_MISMATCH: - Inventory inv = session.getInventoryCache().getOpenInventory(); - if (inv == null) inv = session.getInventory(); - InventoryTranslator.INVENTORY_TRANSLATORS.get(inv.getWindowType()).updateInventory(session, inv); - InventoryUtils.updateCursor(session); break; case ITEM_USE: switch (packet.getActionType()) { @@ -164,9 +184,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { + + @Override + public void translate(ItemStackRequestPacket packet, GeyserSession session) { + Inventory inventory = session.getOpenInventory(); + if (inventory == null) + return; + + InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); + session.addInventoryTask(() -> translator.translateRequests(session, inventory, packet.getRequests())); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java index a220e389f..ade949199 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java @@ -40,12 +40,12 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator 8 || - packet.getContainerId() != ContainerId.INVENTORY || session.getInventory().getHeldItemSlot() == packet.getHotbarSlot()) { + packet.getContainerId() != ContainerId.INVENTORY || session.getPlayerInventory().getHeldItemSlot() == packet.getHotbarSlot()) { // For the last condition - Don't update the slot if the slot is the same - not Java Edition behavior and messes with plugins such as Grief Prevention return; } - session.getInventory().setHeldItemSlot(packet.getHotbarSlot()); + session.getPlayerInventory().setHeldItemSlot(packet.getHotbarSlot()); ClientPlayerChangeHeldItemPacket changeHeldItemPacket = new ClientPlayerChangeHeldItemPacket(packet.getHotbarSlot()); session.sendDownstreamPacket(changeHeldItemPacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/BedrockEntityEventTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/BedrockEntityEventTranslator.java index 18fd6614e..eaf352da9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/BedrockEntityEventTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/BedrockEntityEventTranslator.java @@ -26,12 +26,13 @@ package org.geysermc.connector.network.translators.bedrock.entity; import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; -import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientSelectTradePacket; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.MerchantContainer; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -47,20 +48,25 @@ public class BedrockEntityEventTranslator extends PacketTranslator { + ClientSelectTradePacket selectTradePacket = new ClientSelectTradePacket(packet.getData()); + session.getDownstream().getSession().send(selectTradePacket); + }); - Entity villager = session.getPlayerEntity(); - Inventory openInventory = session.getInventoryCache().getOpenInventory(); - if (openInventory != null && openInventory.getWindowType() == WindowType.MERCHANT) { - VillagerTrade[] trades = session.getVillagerTrades(); - if (trades != null && packet.getData() >= 0 && packet.getData() < trades.length) { - VillagerTrade trade = session.getVillagerTrades()[packet.getData()]; - openInventory.setItem(2, trade.getOutput()); - villager.getMetadata().put(EntityData.TRADE_XP, trade.getXp() + villager.getMetadata().getInt(EntityData.TRADE_XP)); - villager.updateBedrockMetadata(session); + session.addInventoryTask(() -> { + Entity villager = session.getPlayerEntity(); + Inventory openInventory = session.getOpenInventory(); + if (openInventory instanceof MerchantContainer) { + MerchantContainer merchantInventory = (MerchantContainer) openInventory; + VillagerTrade[] trades = merchantInventory.getVillagerTrades(); + if (trades != null && packet.getData() >= 0 && packet.getData() < trades.length) { + VillagerTrade trade = merchantInventory.getVillagerTrades()[packet.getData()]; + openInventory.setItem(2, GeyserItemStack.from(trade.getOutput())); + villager.getMetadata().put(EntityData.TRADE_XP, trade.getXp() + villager.getMetadata().getInt(EntityData.TRADE_XP)); + villager.updateBedrockMetadata(session); + } } - } + }, 100); return; } session.getConnector().getLogger().debug("Did not translate incoming EntityEventPacket: " + packet.toString()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java index c5d6f2dda..ff261aba7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java @@ -60,7 +60,7 @@ public class BedrockInteractTranslator extends PacketTranslator switch (packet.getAction()) { case INTERACT: - if (session.getInventory().getItem(session.getInventory().getHeldItemSlot() + 36).getId() == ItemRegistry.SHIELD.getJavaId()) { + if (session.getPlayerInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) { break; } ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), @@ -135,14 +135,15 @@ public class BedrockInteractTranslator extends PacketTranslator } break; case OPEN_INVENTORY: - if (!session.getInventory().isOpen()) { + if (session.getOpenInventory() == null) { + session.setOpenInventory(session.getPlayerInventory()); + ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); containerOpenPacket.setId((byte) 0); containerOpenPacket.setType(ContainerType.INVENTORY); containerOpenPacket.setUniqueEntityId(-1); containerOpenPacket.setBlockPosition(entity.getPosition().toInt()); session.sendUpstreamPacket(containerOpenPacket); - session.getInventory().setOpen(true); } break; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java deleted file mode 100644 index ab266faef..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; -import com.google.gson.JsonSyntaxException; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.protocol.bedrock.data.inventory.*; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; - -import java.util.List; -import java.util.stream.Collectors; - -public class AnvilInventoryTranslator extends BlockInventoryTranslator { - public AnvilInventoryTranslator() { - super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, new CursorInventoryUpdater()); - } - - @Override - public int bedrockSlotToJava(InventoryActionData action) { - if (action.getSource().getContainerId() == ContainerId.UI) { - switch (action.getSlot()) { - case 1: - return 0; - case 2: - return 1; - case 50: - return 2; - } - } - if (action.getSource().getContainerId() == ContainerId.ANVIL_RESULT) { - return 2; - } - return super.bedrockSlotToJava(action); - } - - @Override - public int javaSlotToBedrock(int slot) { - switch (slot) { - case 0: - return 1; - case 1: - return 2; - case 2: - return 50; - } - return super.javaSlotToBedrock(slot); - } - - @Override - public SlotType getSlotType(int javaSlot) { - if (javaSlot == 2) - return SlotType.OUTPUT; - return SlotType.NORMAL; - } - - @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - InventoryActionData anvilResult = null; - InventoryActionData anvilInput = null; - for (InventoryActionData action : actions) { - if (action.getSource().getContainerId() == ContainerId.ANVIL_MATERIAL) { - //useless packet - return; - } else if (action.getSource().getContainerId() == ContainerId.ANVIL_RESULT) { - anvilResult = action; - } else if (bedrockSlotToJava(action) == 0) { - anvilInput = action; - } - } - ItemData itemName = null; - if (anvilResult != null) { - itemName = anvilResult.getFromItem(); - } else if (anvilInput != null) { - itemName = anvilInput.getToItem(); - } - if (itemName != null) { - String rename; - NbtMap tag = itemName.getTag(); - if (tag != null && tag.containsKey("display")) { - String name = tag.getCompound("display").getString("Name"); - try { - Component component = GsonComponentSerializer.gson().deserialize(name); - rename = LegacyComponentSerializer.legacySection().serialize(component); - } catch (JsonSyntaxException e) { - rename = name; - } - } else { - rename = ""; - } - ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename); - session.sendDownstreamPacket(renameItemPacket); - } - if (anvilResult != null) { - //Strip unnecessary actions - List strippedActions = actions.stream() - .filter(action -> action.getSource().getContainerId() == ContainerId.ANVIL_RESULT - || (action.getSource().getType() == InventorySource.Type.CONTAINER - && !(action.getSource().getContainerId() == ContainerId.UI && action.getSlot() != 0))) - .collect(Collectors.toList()); - super.translateActions(session, inventory, strippedActions); - return; - } - - super.translateActions(session, inventory, actions); - } - - @Override - public void updateSlot(GeyserSession session, Inventory inventory, int slot) { - if (slot == 0) { - ItemStack item = inventory.getItem(slot); - if (item != null) { - String rename; - CompoundTag tag = item.getNbt(); - if (tag != null) { - CompoundTag displayTag = tag.get("display"); - if (displayTag != null && displayTag.contains("Name")) { - String itemName = displayTag.get("Name").getValue().toString(); - try { - Component component = GsonComponentSerializer.gson().deserialize(itemName); - rename = LegacyComponentSerializer.legacySection().serialize(component); - } catch (JsonSyntaxException e) { - rename = itemName; - } - } else { - rename = ""; - } - } else { - rename = ""; - } - ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename); - session.sendDownstreamPacket(renameItemPacket); - } - } - super.updateSlot(session, inventory, slot); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java index 6f00fc4d7..7d54667bd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java @@ -25,15 +25,15 @@ package org.geysermc.connector.network.translators.inventory; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.connector.inventory.Container; import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.action.InventoryActionDataTranslator; -import java.util.List; - -public abstract class BaseInventoryTranslator extends InventoryTranslator{ +public abstract class BaseInventoryTranslator extends InventoryTranslator { BaseInventoryTranslator(int size) { super(size); } @@ -44,15 +44,18 @@ public abstract class BaseInventoryTranslator extends InventoryTranslator{ } @Override - public int bedrockSlotToJava(InventoryActionData action) { - int slotnum = action.getSlot(); - if (action.getSource().getContainerId() == ContainerId.INVENTORY) { - //hotbar - if (slotnum >= 9) { - return slotnum + this.size - 9; - } else { - return slotnum + this.size + 27; - } + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + int slotnum = slotInfoData.getSlot(); + switch (slotInfoData.getContainer()) { + case HOTBAR_AND_INVENTORY: + case HOTBAR: + case INVENTORY: + //hotbar + if (slotnum >= 9) { + return slotnum + this.size - 9; + } else { + return slotnum + this.size + 27; + } } return slotnum; } @@ -70,13 +73,26 @@ public abstract class BaseInventoryTranslator extends InventoryTranslator{ return slot; } + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot >= this.size) { + final int tmp = slot - this.size; + if (tmp < 27) { + return new BedrockContainerSlot(ContainerSlotType.INVENTORY, tmp + 9); + } else { + return new BedrockContainerSlot(ContainerSlotType.HOTBAR, tmp - 27); + } + } + throw new IllegalArgumentException("Unknown bedrock slot"); + } + @Override public SlotType getSlotType(int javaSlot) { return SlotType.NORMAL; } @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - InventoryActionDataTranslator.translate(this, session, inventory, actions); + public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { + return new Container(name, windowId, windowType, this.size, playerInventory); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BedrockContainerSlot.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BedrockContainerSlot.java new file mode 100644 index 000000000..b3a09e167 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BedrockContainerSlot.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + * + */ + +package org.geysermc.connector.network.translators.inventory; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import lombok.Value; + +@Value +public class BedrockContainerSlot { + ContainerSlotType container; + int slot; +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java deleted file mode 100644 index 4b8b57e8f..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; -import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; -import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; - -public class BlockInventoryTranslator extends BaseInventoryTranslator { - private final InventoryHolder holder; - private final InventoryUpdater updater; - - public BlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater) { - super(size); - int javaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier); - int blockId = BlockTranslator.getBedrockBlockId(javaBlockState); - this.holder = new BlockInventoryHolder(blockId, containerType); - this.updater = updater; - } - - @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { - holder.prepareInventory(this, session, inventory); - } - - @Override - public void openInventory(GeyserSession session, Inventory inventory) { - holder.openInventory(this, session, inventory); - } - - @Override - public void closeInventory(GeyserSession session, Inventory inventory) { - holder.closeInventory(this, session, inventory); - } - - @Override - public void updateInventory(GeyserSession session, Inventory inventory) { - updater.updateInventory(this, session, inventory); - } - - @Override - public void updateSlot(GeyserSession session, Inventory inventory, int slot) { - updater.updateSlot(this, session, inventory, slot); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java deleted file mode 100644 index 89cdbe8d7..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; - -public class BrewingInventoryTranslator extends BlockInventoryTranslator { - public BrewingInventoryTranslator() { - super(5, "minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=false]", ContainerType.BREWING_STAND, new ContainerInventoryUpdater()); - } - - @Override - public void openInventory(GeyserSession session, Inventory inventory) { - super.openInventory(session, inventory); - ContainerSetDataPacket dataPacket = new ContainerSetDataPacket(); - dataPacket.setWindowId((byte) inventory.getId()); - dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_FUEL_TOTAL); - dataPacket.setValue(20); - session.sendUpstreamPacket(dataPacket); - } - - @Override - public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { - ContainerSetDataPacket dataPacket = new ContainerSetDataPacket(); - dataPacket.setWindowId((byte) inventory.getId()); - switch (key) { - case 0: - dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_BREW_TIME); - break; - case 1: - dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_FUEL_AMOUNT); - break; - default: - return; - } - dataPacket.setValue(value); - session.sendUpstreamPacket(dataPacket); - } - - @Override - public int bedrockSlotToJava(InventoryActionData action) { - final int slot = super.bedrockSlotToJava(action); - switch (slot) { - case 0: - return 3; - case 1: - return 0; - case 2: - return 1; - case 3: - return 2; - default: - return slot; - } - } - - @Override - public int javaSlotToBedrock(int slot) { - switch (slot) { - case 0: - return 1; - case 1: - return 2; - case 2: - return 3; - case 3: - return 0; - } - return super.javaSlotToBedrock(slot); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java index 152f4a852..df97563e5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java @@ -25,14 +25,11 @@ package org.geysermc.connector.network.translators.inventory; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater; import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; -import org.geysermc.connector.utils.InventoryUtils; - -import java.util.List; public abstract class ChestInventoryTranslator extends BaseInventoryTranslator { private final InventoryUpdater updater; @@ -53,17 +50,10 @@ public abstract class ChestInventoryTranslator extends BaseInventoryTranslator { } @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - for (InventoryActionData action : actions) { - if (action.getSource().getContainerId() == inventory.getId()) { - if (action.getSlot() >= size) { - updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - } - } + public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) { + if (javaSlot < this.size) { + return new BedrockContainerSlot(ContainerSlotType.CONTAINER, javaSlot); } - - super.translateActions(session, inventory, actions); + return super.javaSlotToBedrockContainer(javaSlot); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java deleted file mode 100644 index b260565b8..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import com.nukkitx.protocol.bedrock.data.inventory.InventorySource; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; -import org.geysermc.connector.utils.InventoryUtils; - -import java.util.List; - -public class CraftingInventoryTranslator extends BlockInventoryTranslator { - public CraftingInventoryTranslator() { - super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, new CursorInventoryUpdater()); - } - - @Override - public int bedrockSlotToJava(InventoryActionData action) { - if (action.getSlot() == 50) { - // Slot 50 is used for crafting with a controller. - return 0; - } - - if (action.getSource().getContainerId() == ContainerId.UI) { - int slotnum = action.getSlot(); - if (slotnum >= 32 && 42 >= slotnum) { - return slotnum - 31; - } - } - return super.bedrockSlotToJava(action); - } - - @Override - public int javaSlotToBedrock(int slot) { - if (slot < size) { - return slot == 0 ? 50 : slot + 31; - } - return super.javaSlotToBedrock(slot); - } - - @Override - public SlotType getSlotType(int javaSlot) { - if (javaSlot == 0) - return SlotType.OUTPUT; - return SlotType.NORMAL; - } - - @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - if (session.getGameMode() == GameMode.CREATIVE) { - for (InventoryActionData action : actions) { - if (action.getSource().getType() == InventorySource.Type.CREATIVE) { - updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - } - } - } - super.translateActions(session, inventory, actions); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java deleted file mode 100644 index cbcdce10b..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket; -import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtMapBuilder; -import com.nukkitx.nbt.NbtType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; -import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; -import org.geysermc.connector.common.ChatColor; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.utils.InventoryUtils; -import org.geysermc.connector.utils.LocaleUtils; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * A temporary reconstruction of the enchantment table UI until our inventory rewrite is complete. - * The enchantment table on Bedrock without server authoritative inventories doesn't tell us which button is pressed - * when selecting an enchantment. - */ -public class EnchantmentInventoryTranslator extends BlockInventoryTranslator { - - private static final int DYE_ID = 351; - private static final short LAPIS_DAMAGE = 4; - private static final int ENCHANTED_BOOK_ID = 403; - - public EnchantmentInventoryTranslator(InventoryUpdater updater) { - super(2, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, updater); - } - - @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - for (InventoryActionData action : actions) { - if (action.getSource().getContainerId() == inventory.getId()) { - // This is the hopper UI - switch (action.getSlot()) { - case 1: - // Don't allow the slot to be put through if the item isn't lapis - if ((action.getToItem().getId() != DYE_ID - && action.getToItem().getDamage() != LAPIS_DAMAGE) && action.getToItem() != ItemData.AIR) { - updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - } - break; - case 2: - case 3: - case 4: - // The books here act as buttons - ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), action.getSlot() - 2); - session.sendDownstreamPacket(packet); - updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - default: - break; - } - } - } - - super.translateActions(session, inventory, actions); - } - - @Override - public void updateInventory(GeyserSession session, Inventory inventory) { - super.updateInventory(session, inventory); - ItemData[] items = new ItemData[5]; - items[0] = ItemTranslator.translateToBedrock(session, inventory.getItem(0)); - items[1] = ItemTranslator.translateToBedrock(session, inventory.getItem(1)); - for (int i = 0; i < 3; i++) { - items[i + 2] = session.getEnchantmentSlotData()[i].getItem() != null ? session.getEnchantmentSlotData()[i].getItem() : createEnchantmentBook(); - } - - InventoryContentPacket contentPacket = new InventoryContentPacket(); - contentPacket.setContainerId(inventory.getId()); - contentPacket.setContents(items); - session.sendUpstreamPacket(contentPacket); - } - - @Override - public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { - int bookSlotToUpdate; - switch (key) { - case 0: - case 1: - case 2: - // Experience required - bookSlotToUpdate = key; - session.getEnchantmentSlotData()[bookSlotToUpdate].setExperienceRequired(value); - break; - case 4: - case 5: - case 6: - // Enchantment name - bookSlotToUpdate = key - 4; - if (value != -1) { - session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentType(EnchantmentTableEnchantments.values()[value - 1]); - } else { - // -1 means no enchantment specified - session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentType(null); - } - break; - case 7: - case 8: - case 9: - // Enchantment level - bookSlotToUpdate = key - 7; - session.getEnchantmentSlotData()[bookSlotToUpdate].setEnchantmentLevel(value); - break; - default: - return; - } - updateEnchantmentBook(session, inventory, bookSlotToUpdate); - } - - @Override - public void openInventory(GeyserSession session, Inventory inventory) { - super.openInventory(session, inventory); - for (int i = 0; i < session.getEnchantmentSlotData().length; i++) { - session.getEnchantmentSlotData()[i] = new EnchantmentSlotData(); - } - } - - @Override - public void closeInventory(GeyserSession session, Inventory inventory) { - super.closeInventory(session, inventory); - Arrays.fill(session.getEnchantmentSlotData(), null); - } - - private ItemData createEnchantmentBook() { - NbtMapBuilder root = NbtMap.builder(); - NbtMapBuilder display = NbtMap.builder(); - - display.putString("Name", ChatColor.RESET + "No Enchantment"); - - root.put("display", display.build()); - return ItemData.of(ENCHANTED_BOOK_ID, (short) 0, 1, root.build()); - } - - private void updateEnchantmentBook(GeyserSession session, Inventory inventory, int slot) { - NbtMapBuilder root = NbtMap.builder(); - NbtMapBuilder display = NbtMap.builder(); - EnchantmentSlotData data = session.getEnchantmentSlotData()[slot]; - if (data.getEnchantmentType() != null) { - display.putString("Name", ChatColor.ITALIC + data.getEnchantmentType().toString(session) + - (data.getEnchantmentLevel() != -1 ? " " + toRomanNumeral(session, data.getEnchantmentLevel()) : "") + "?"); - } else { - display.putString("Name", ChatColor.RESET + "No Enchantment"); - } - - display.putList("Lore", NbtType.STRING, Collections.singletonList(ChatColor.DARK_GRAY + data.getExperienceRequired() + "xp")); - root.put("display", display.build()); - ItemData book = ItemData.of(ENCHANTED_BOOK_ID, (short) 0, 1, root.build()); - - InventorySlotPacket slotPacket = new InventorySlotPacket(); - slotPacket.setContainerId(inventory.getId()); - slotPacket.setSlot(slot + 2); - slotPacket.setItem(book); - session.sendUpstreamPacket(slotPacket); - data.setItem(book); - } - - private String toRomanNumeral(GeyserSession session, int level) { - return LocaleUtils.getLocaleString("enchantment.level." + level, - session.getClientData().getLanguageCode()); - } - - /** - * Stores the data of each slot in an enchantment table - */ - @NoArgsConstructor - @Getter - @Setter - @ToString - public static class EnchantmentSlotData { - private EnchantmentTableEnchantments enchantmentType = null; - private int enchantmentLevel = 0; - private int experienceRequired = 0; - private ItemData item; - } - - /** - * Classifies enchantments by Java order - */ - public enum EnchantmentTableEnchantments { - PROTECTION, - FIRE_PROTECTION, - FEATHER_FALLING, - BLAST_PROTECTION, - PROJECTILE_PROTECTION, - RESPIRATION, - AQUA_AFFINITY, - THORNS, - DEPTH_STRIDER, - FROST_WALKER, - BINDING_CURSE, - SHARPNESS, - SMITE, - BANE_OF_ARTHROPODS, - KNOCKBACK, - FIRE_ASPECT, - LOOTING, - SWEEPING, - EFFICIENCY, - SILK_TOUCH, - UNBREAKING, - FORTUNE, - POWER, - PUNCH, - FLAME, - INFINITY, - LUCK_OF_THE_SEA, - LURE, - LOYALTY, - IMPALING, - RIPTIDE, - CHANNELING, - MENDING, - VANISHING_CURSE, // After this is not documented - MULTISHOT, - PIERCING, - QUICK_CHARGE, - SOUL_SPEED; - - public String toString(GeyserSession session) { - return LocaleUtils.getLocaleString("enchantment.minecraft." + this.toString().toLowerCase(), - session.getClientData().getLanguageCode()); - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java deleted file mode 100644 index 1f148e024..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.github.steveice10.mc.protocol.data.game.window.WindowType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; - -public class FurnaceInventoryTranslator extends BlockInventoryTranslator { - public FurnaceInventoryTranslator() { - super(3, "minecraft:furnace[facing=north,lit=false]", ContainerType.FURNACE, new ContainerInventoryUpdater()); - } - - @Override - public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { - ContainerSetDataPacket dataPacket = new ContainerSetDataPacket(); - dataPacket.setWindowId((byte) inventory.getId()); - switch (key) { - case 0: - dataPacket.setProperty(ContainerSetDataPacket.FURNACE_LIT_TIME); - break; - case 1: - dataPacket.setProperty(ContainerSetDataPacket.FURNACE_LIT_DURATION); - break; - case 2: - dataPacket.setProperty(ContainerSetDataPacket.FURNACE_TICK_COUNT); - if (inventory.getWindowType() == WindowType.BLAST_FURNACE || inventory.getWindowType() == WindowType.SMOKER) { - value *= 2; - } - break; - default: - return; - } - dataPacket.setValue(value); - session.sendUpstreamPacket(dataPacket); - } - - @Override - public SlotType getSlotType(int javaSlot) { - if (javaSlot == 2) - return SlotType.FURNACE_OUTPUT; - return SlotType.NORMAL; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java deleted file mode 100644 index 4b4a12465..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; - -public class GrindstoneInventoryTranslator extends BlockInventoryTranslator { - - public GrindstoneInventoryTranslator() { - super(3, "minecraft:grindstone[face=floor,facing=north]", ContainerType.GRINDSTONE, new CursorInventoryUpdater()); - } - - @Override - public int bedrockSlotToJava(InventoryActionData action) { - final int slot = super.bedrockSlotToJava(action); - if (action.getSource().getContainerId() == ContainerId.UI) { - switch (slot) { - case 16: - return 0; - case 17: - return 1; - case 50: - return 2; - default: - return slot; - } - } return slot; - } - - @Override - public int javaSlotToBedrock(int slot) { - switch (slot) { - case 0: - return 16; - case 1: - return 17; - case 2: - return 50; - } - return super.javaSlotToBedrock(slot); - } - -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index e44e4bd0b..b4a12b00f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -25,18 +25,26 @@ package org.geysermc.connector.network.translators.inventory; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.window.WindowType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; +import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import lombok.AllArgsConstructor; +import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; -import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; +import org.geysermc.connector.network.translators.inventory.click.Click; +import org.geysermc.connector.network.translators.inventory.click.ClickPlan; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.utils.InventoryUtils; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; @AllArgsConstructor public abstract class InventoryTranslator { @@ -50,12 +58,13 @@ public abstract class InventoryTranslator { put(WindowType.GENERIC_9X4, new DoubleChestInventoryTranslator(36)); put(WindowType.GENERIC_9X5, new DoubleChestInventoryTranslator(45)); put(WindowType.GENERIC_9X6, new DoubleChestInventoryTranslator(54)); - put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); + /*put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); put(WindowType.ANVIL, new AnvilInventoryTranslator()); put(WindowType.CRAFTING, new CraftingInventoryTranslator()); - put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); + put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator());*/ put(WindowType.MERCHANT, new MerchantInventoryTranslator()); - put(WindowType.SMITHING, new SmithingInventoryTranslator()); + /*put(WindowType.SMITHING, new SmithingInventoryTranslator()); + //put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator()); //TODO InventoryTranslator furnace = new FurnaceInventoryTranslator(); put(WindowType.FURNACE, furnace); @@ -63,14 +72,15 @@ public abstract class InventoryTranslator { put(WindowType.SMOKER, furnace); InventoryUpdater containerUpdater = new ContainerInventoryUpdater(); - put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator(containerUpdater)); //TODO put(WindowType.GENERIC_3X3, new BlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, containerUpdater)); put(WindowType.HOPPER, new BlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, containerUpdater)); put(WindowType.SHULKER_BOX, new BlockInventoryTranslator(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, containerUpdater)); - //put(WindowType.BEACON, new BlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO + //put(WindowType.BEACON, new BlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO*/ } }; + public static final int PLAYER_INVENTORY_SIZE = 36; + public static final int PLAYER_INVENTORY_OFFSET = 9; public final int size; public abstract void prepareInventory(GeyserSession session, Inventory inventory); @@ -79,8 +89,428 @@ public abstract class InventoryTranslator { public abstract void updateProperty(GeyserSession session, Inventory inventory, int key, int value); public abstract void updateInventory(GeyserSession session, Inventory inventory); public abstract void updateSlot(GeyserSession session, Inventory inventory, int slot); - public abstract int bedrockSlotToJava(InventoryActionData action); - public abstract int javaSlotToBedrock(int slot); + public abstract int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData); + public abstract int javaSlotToBedrock(int javaSlot); //TODO + public abstract BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot); //TODO public abstract SlotType getSlotType(int javaSlot); - public abstract void translateActions(GeyserSession session, Inventory inventory, List actions); + public abstract Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory); + + public void translateRequests(GeyserSession session, Inventory inventory, List requests) { + ItemStackResponsePacket responsePacket = new ItemStackResponsePacket(); + for (ItemStackRequestPacket.Request request : requests) { + if (request.getActions().length > 0) { + StackRequestActionData firstAction = request.getActions()[0]; + if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE || firstAction.getType() == StackRequestActionType.CRAFT_RECIPE_AUTO) { + responsePacket.getEntries().add(translateCraftingRequest(session, inventory, request)); + } else if (firstAction.getType() == StackRequestActionType.CRAFT_CREATIVE) { + responsePacket.getEntries().add(translateCreativeRequest(session, inventory, request)); + } else { + responsePacket.getEntries().add(translateRequest(session, inventory, request)); + } + } else { + responsePacket.getEntries().add(rejectRequest(request)); + } + } + session.sendUpstreamPacket(responsePacket); + System.out.println(responsePacket); + } + + public ItemStackResponsePacket.Response translateRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + System.out.println(request); + ClickPlan plan = new ClickPlan(session, this, inventory); + for (StackRequestActionData action : request.getActions()) { + GeyserItemStack cursor = session.getPlayerInventory().getCursor(); + switch (action.getType()) { + case TAKE: + case PLACE: { + TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; + if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) + return rejectRequest(request); + + if (isCursor(transferAction.getSource()) && isCursor(transferAction.getDestination())) { //??? + return rejectRequest(request); + } else if (isCursor(transferAction.getSource())) { //releasing cursor + int sourceAmount = cursor.getAmount(); + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + if (transferAction.getCount() == sourceAmount) { //release all + plan.add(Click.LEFT, destSlot); + } else { //release some + for (int i = 0; i < transferAction.getCount(); i++) { + plan.add(Click.RIGHT, destSlot); + } + } + } else if (isCursor(transferAction.getDestination())) { //picking up into cursor + int sourceSlot = bedrockSlotToJava(transferAction.getSource()); + GeyserItemStack sourceItem = plan.getItem(sourceSlot); + int sourceAmount = sourceItem.getAmount(); + if (cursor.isEmpty()) { //picking up into empty cursor + if (transferAction.getCount() == sourceAmount) { //pickup all + plan.add(Click.LEFT, sourceSlot); + } else if (transferAction.getCount() == sourceAmount - (sourceAmount / 2)) { //larger half; simple right click + plan.add(Click.RIGHT, sourceSlot); + } else { //pickup some; not a simple right click + plan.add(Click.LEFT, sourceSlot); //first pickup all + for (int i = 0; i < sourceAmount - transferAction.getCount(); i++) { + plan.add(Click.RIGHT, sourceSlot); //release extra items back into source slot + } + } + } else { //pickup into non-empty cursor + if (!InventoryUtils.canStack(cursor, plan.getItem(sourceSlot))) { //doesn't make sense, reject + return rejectRequest(request); + } + if (transferAction.getCount() != sourceAmount) { //TODO: handle partially picking up into non-empty cursor (temp slot) + return rejectRequest(request); + } + plan.add(Click.LEFT, sourceSlot); //release cursor onto source slot + plan.add(Click.LEFT, sourceSlot); //pickup combined cursor and source + } + } else { //transfer from one slot to another + if (!cursor.isEmpty()) { //TODO: handle slot transfer when cursor is already in use (temp slot) + return rejectRequest(request); + } + int sourceSlot = bedrockSlotToJava(transferAction.getSource()); + int sourceAmount = plan.getItem(sourceSlot).getAmount(); + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + if (transferAction.getCount() == sourceAmount) { //transfer all + plan.add(Click.LEFT, sourceSlot); //pickup source + plan.add(Click.LEFT, destSlot); //let go of all items and done + } else { //transfer some + //try to transfer items with least clicks possible + int halfSource = sourceAmount / 2; //smaller half + int holding; + if (transferAction.getCount() <= halfSource) { //faster to take only half + plan.add(Click.RIGHT, sourceSlot); + holding = halfSource; + } else { //need all + plan.add(Click.LEFT, sourceSlot); + holding = sourceAmount; + } + if (transferAction.getCount() > holding / 2) { //faster to release extra items onto source or dest slot? + for (int i = 0; i < holding - transferAction.getCount(); i++) { + plan.add(Click.RIGHT, sourceSlot); //prepare cursor + } + plan.add(Click.LEFT, destSlot); //release cursor onto dest slot + } else { + for (int i = 0; i < transferAction.getCount(); i++) { + plan.add(Click.RIGHT, destSlot); //right click until transfer goal is met + } + plan.add(Click.LEFT, sourceSlot); //return extra items to source slot + } + } + } + break; + } + case SWAP: { //TODO + SwapStackRequestActionData swapAction = (SwapStackRequestActionData) action; + if (!(checkNetId(session, inventory, swapAction.getSource()) && checkNetId(session, inventory, swapAction.getDestination()))) + return rejectRequest(request); + + if (isCursor(swapAction.getSource()) && isCursor(swapAction.getDestination())) { //??? + return rejectRequest(request); + } else if (isCursor(swapAction.getSource())) { //swap cursor + int destSlot = bedrockSlotToJava(swapAction.getDestination()); + if (InventoryUtils.canStack(cursor, plan.getItem(destSlot))) { //TODO: cannot simply swap if cursor stacks with slot (temp slot) + return rejectRequest(request); + } + plan.add(Click.LEFT, destSlot); + } else if (isCursor(swapAction.getDestination())) { //swap cursor + int sourceSlot = bedrockSlotToJava(swapAction.getSource()); + if (InventoryUtils.canStack(cursor, plan.getItem(sourceSlot))) { //TODO + return rejectRequest(request); + } + plan.add(Click.LEFT, sourceSlot); + } else { + int sourceSlot = bedrockSlotToJava(swapAction.getSource()); + int destSlot = bedrockSlotToJava(swapAction.getDestination()); + if (!cursor.isEmpty()) { //TODO: (temp slot) + return rejectRequest(request); + } + if (sourceSlot == destSlot) { //doesn't make sense + return rejectRequest(request); + } + if (InventoryUtils.canStack(plan.getItem(sourceSlot), plan.getItem(destSlot))) { //TODO: (temp slot) + return rejectRequest(request); + } + plan.add(Click.LEFT, sourceSlot); //pickup source into cursor + plan.add(Click.LEFT, destSlot); //swap cursor with dest slot + plan.add(Click.LEFT, sourceSlot); //release cursor onto source + } + break; + } + case DROP: { + DropStackRequestActionData dropAction = (DropStackRequestActionData) action; + if (!checkNetId(session, inventory, dropAction.getSource())) + return rejectRequest(request); + + if (isCursor(dropAction.getSource())) { //clicking outside of window + int sourceAmount = plan.getCursor().getAmount(); + if (dropAction.getCount() == sourceAmount) { //drop all + plan.add(Click.LEFT_OUTSIDE, Click.OUTSIDE_SLOT); + } else { //drop some + for (int i = 0; i < dropAction.getCount(); i++) { + plan.add(Click.RIGHT_OUTSIDE, Click.OUTSIDE_SLOT); //drop one until goal is met + } + } + } else { //dropping from inventory + int sourceSlot = bedrockSlotToJava(dropAction.getSource()); + int sourceAmount = plan.getItem(sourceSlot).getAmount(); + if (dropAction.getCount() == sourceAmount && sourceAmount > 1) { //dropping all? (prefer DROP_ONE if only one) + plan.add(Click.DROP_ALL, sourceSlot); + } else { //drop some + for (int i = 0; i < dropAction.getCount(); i++) { + plan.add(Click.DROP_ONE, sourceSlot); //drop one until goal is met + } + } + } + break; + } + case CRAFT_CREATIVE: { + CraftCreativeStackRequestActionData creativeAction = (CraftCreativeStackRequestActionData) action; + System.out.println(creativeAction.getCreativeItemNetworkId()); + } + default: + return rejectRequest(request); + } + } + plan.execute(false); + return acceptRequest(request, makeContainerEntries(session, inventory, plan.getAffectedSlots())); + } + + public ItemStackResponsePacket.Response translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + System.out.println(request); + + int recipeId = 0; + int resultSize = 0; + boolean autoCraft; + CraftState craftState = CraftState.START; + + int leftover = 0; + ClickPlan plan = new ClickPlan(session, this, inventory); + for (StackRequestActionData action : request.getActions()) { + switch (action.getType()) { + case CRAFT_RECIPE: { + CraftRecipeStackRequestActionData craftAction = (CraftRecipeStackRequestActionData) action; + if (craftState != CraftState.START) { + return rejectRequest(request); + } + craftState = CraftState.RECIPE_ID; + recipeId = craftAction.getRecipeNetworkId(); + System.out.println(session.getCraftingRecipes().get(recipeId).toString()); + autoCraft = false; + break; + } + /*case CRAFT_RECIPE_AUTO: { + AutoCraftRecipeStackRequestActionData autoCraftAction = (AutoCraftRecipeStackRequestActionData) action; + if (craftState != CraftState.START) { + return rejectRequest(request); + } + craftState = CraftState.RECIPE_ID; + recipeId = autoCraftAction.getRecipeNetworkId(); + autoCraft = true; + //TODO: reject transaction if crafting grid is not clear + break; + }*/ + case CRAFT_RESULTS_DEPRECATED: { + CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action; + if (craftState != CraftState.RECIPE_ID) { + return rejectRequest(request); + } + craftState = CraftState.DEPRECATED; + + if (deprecatedCraftAction.getResultItems().length != 1) { + return rejectRequest(request); + } + resultSize = deprecatedCraftAction.getResultItems()[0].getCount(); + if (resultSize <= 0) { + return rejectRequest(request); + } + break; + } + case CONSUME: { + ConsumeStackRequestActionData consumeAction = (ConsumeStackRequestActionData) action; + if (craftState != CraftState.DEPRECATED && craftState != CraftState.INGREDIENTS) { + return rejectRequest(request); + } + craftState = CraftState.INGREDIENTS; + break; + } + case TAKE: + case PLACE: { + TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; + if (craftState != CraftState.INGREDIENTS && craftState != CraftState.TRANSFER) { + return rejectRequest(request); + } + craftState = CraftState.TRANSFER; + + if (transferAction.getSource().getContainer() != ContainerSlotType.CREATIVE_OUTPUT) { + return rejectRequest(request); + } + if (transferAction.getCount() <= 0) { + return rejectRequest(request); + } + + int sourceSlot = bedrockSlotToJava(transferAction.getSource()); + if (isCursor(transferAction.getDestination())) { + plan.add(Click.LEFT, sourceSlot); + craftState = CraftState.DONE; + } else { + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + if (leftover != 0) { + if (transferAction.getCount() > leftover) { + return rejectRequest(request); + } + if (transferAction.getCount() == leftover) { + plan.add(Click.LEFT, destSlot); + } else { + for (int i = 0; i < transferAction.getCount(); i++) { + plan.add(Click.RIGHT, destSlot); + } + } + leftover -= transferAction.getCount(); + break; + } + + int remainder = transferAction.getCount() % resultSize; + int timesToCraft = transferAction.getCount() / resultSize; + for (int i = 0; i < timesToCraft; i++) { + plan.add(Click.LEFT, sourceSlot); + plan.add(Click.LEFT, destSlot); + } + if (remainder > 0) { + plan.add(Click.LEFT, 0); + for (int i = 0; i < remainder; i++) { + plan.add(Click.RIGHT, destSlot); + } + leftover = resultSize - remainder; + } + } + break; + } + default: + return rejectRequest(request); + } + } + plan.execute(false); + Set affectedSlots = plan.getAffectedSlots(); + affectedSlots.addAll(Arrays.asList(1, 2, 3, 4)); //TODO: crafting grid + return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); + } + + public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + int creativeId = 0; + CraftState craftState = CraftState.START; + for (StackRequestActionData action : request.getActions()) { + switch (action.getType()) { + case CRAFT_CREATIVE: { + CraftCreativeStackRequestActionData creativeAction = (CraftCreativeStackRequestActionData) action; + if (craftState != CraftState.START) { + return rejectRequest(request); + } + craftState = CraftState.RECIPE_ID; + + creativeId = creativeAction.getCreativeItemNetworkId(); + break; + } + case CRAFT_RESULTS_DEPRECATED: { + CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action; + if (craftState != CraftState.RECIPE_ID) { + return rejectRequest(request); + } + craftState = CraftState.DEPRECATED; + break; + } + case TAKE: + case PLACE: { + TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; + if (craftState != CraftState.DEPRECATED) { + return rejectRequest(request); + } + craftState = CraftState.TRANSFER; + + if (transferAction.getSource().getContainer() != ContainerSlotType.CREATIVE_OUTPUT) { + return rejectRequest(request); + } + if (isCursor(transferAction.getDestination())) { + session.getPlayerInventory().setCursor(GeyserItemStack.from(ItemTranslator.translateToJava(ItemRegistry.CREATIVE_ITEMS[creativeId]), session.getItemNetId().getAndIncrement())); //TODO + return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); + } else { + int javaSlot = bedrockSlotToJava(transferAction.getDestination()); + ItemStack javaItem = ItemTranslator.translateToJava(ItemRegistry.CREATIVE_ITEMS[creativeId - 1]); //TODO + inventory.setItem(javaSlot, GeyserItemStack.from(javaItem, session.getItemNetId().getAndIncrement())); + ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( + javaSlot, + javaItem + ); + session.sendDownstreamPacket(creativeActionPacket); + Set affectedSlots = Collections.singleton(javaSlot); + return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); + } + } + default: + return rejectRequest(request); + } + } + return rejectRequest(request); + } + + public static ItemStackResponsePacket.Response acceptRequest(ItemStackRequestPacket.Request request, List containerEntries) { + return new ItemStackResponsePacket.Response(true, request.getRequestId(), containerEntries); + } + + public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequestPacket.Request request) { + new Throwable("DEBUGGING: ItemStackRequest rejected").printStackTrace(); //TODO: temporary debugging + return new ItemStackResponsePacket.Response(false, request.getRequestId(), Collections.emptyList()); + } + + public boolean checkNetId(GeyserSession session, Inventory inventory, StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getStackNetworkId() < 0) + return true; + + GeyserItemStack currentItem = isCursor(slotInfoData) ? session.getPlayerInventory().getCursor() : inventory.getItem(bedrockSlotToJava(slotInfoData)); + return currentItem.getNetId() == slotInfoData.getStackNetworkId(); + } + + public List makeContainerEntries(GeyserSession session, Inventory inventory, Set affectedSlots) { + Map> containerMap = new HashMap<>(); + for (int slot : affectedSlots) { + BedrockContainerSlot bedrockSlot = javaSlotToBedrockContainer(slot); + List list = containerMap.computeIfAbsent(bedrockSlot.getContainer(), k -> new ArrayList<>()); + list.add(makeItemEntry(session, bedrockSlot.getSlot(), inventory.getItem(slot))); + } + + List containerEntries = new ArrayList<>(); + for (Map.Entry> entry : containerMap.entrySet()) { + containerEntries.add(new ItemStackResponsePacket.ContainerEntry(entry.getKey(), entry.getValue())); + } + + ItemStackResponsePacket.ItemEntry cursorEntry = makeItemEntry(session, 0, session.getPlayerInventory().getCursor()); + containerEntries.add(new ItemStackResponsePacket.ContainerEntry(ContainerSlotType.CURSOR, Collections.singletonList(cursorEntry))); + + return containerEntries; + } + + public static ItemStackResponsePacket.ItemEntry makeItemEntry(GeyserSession session, int bedrockSlot, GeyserItemStack itemStack) { + ItemStackResponsePacket.ItemEntry itemEntry; + if (!itemStack.isEmpty()) { + int newNetId = session.getItemNetId().getAndIncrement(); + itemStack.setNetId(newNetId); + itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) itemStack.getAmount(), newNetId); + } else { + itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) 0, 0); + } + return itemEntry; + } + + private static boolean isCursor(StackRequestSlotInfoData slotInfoData) { + return slotInfoData.getContainer() == ContainerSlotType.CURSOR; + } + + private enum CraftState { + START, + RECIPE_ID, + DEPRECATED, + INGREDIENTS, + TRANSFER, + DONE + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/MerchantInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/MerchantInventoryTranslator.java index 25fdae688..c4b2fc9a1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/MerchantInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/MerchantInventoryTranslator.java @@ -25,15 +25,21 @@ package org.geysermc.connector.network.translators.inventory; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import com.nukkitx.protocol.bedrock.data.inventory.InventorySource; +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; +import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; +import com.nukkitx.protocol.bedrock.data.inventory.*; +import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.MerchantContainer; +import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; - -import java.util.List; +import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; public class MerchantInventoryTranslator extends BaseInventoryTranslator { @@ -41,7 +47,7 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator { public MerchantInventoryTranslator() { super(3); - this.updater = new CursorInventoryUpdater(); + this.updater = new UIInventoryUpdater(); } @Override @@ -58,26 +64,30 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator { } @Override - public int bedrockSlotToJava(InventoryActionData action) { - switch (action.getSource().getContainerId()) { - case ContainerId.UI: - switch (action.getSlot()) { - case 4: - return 0; - case 5: - return 1; - case 50: - return 2; - } - break; - case -28: // Trading 1? + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + switch (slot) { + case 0: + return new BedrockContainerSlot(ContainerSlotType.TRADE2_INGREDIENT1, 4); + case 1: + return new BedrockContainerSlot(ContainerSlotType.TRADE2_INGREDIENT2, 5); + case 2: + return new BedrockContainerSlot(ContainerSlotType.TRADE2_RESULT, 50); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + switch (slotInfoData.getContainer()) { + case TRADE2_INGREDIENT1: return 0; - case -29: // Trading 2? + case TRADE2_INGREDIENT2: return 1; - case -30: // Trading Output? + case TRADE2_RESULT: + case CREATIVE_OUTPUT: return 2; } - return super.bedrockSlotToJava(action); + return super.bedrockSlotToJava(slotInfoData); } @Override @@ -90,18 +100,40 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator { @Override public void prepareInventory(GeyserSession session, Inventory inventory) { + MerchantContainer merchantInventory = (MerchantContainer) inventory; + if (merchantInventory.getVillager() == null) { + long geyserId = session.getEntityCache().getNextEntityId().incrementAndGet(); + Vector3f pos = session.getPlayerEntity().getPosition().sub(0, 3, 0); + EntityDataMap metadata = new EntityDataMap(); + metadata.put(EntityData.SCALE, 0f); + metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0f); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0f); + + Entity villager = new Entity(0, geyserId, EntityType.VILLAGER, pos, Vector3f.ZERO, Vector3f.ZERO); + villager.setMetadata(metadata); + villager.spawnEntity(session); + + SetEntityLinkPacket linkPacket = new SetEntityLinkPacket(); + EntityLinkData.Type type = EntityLinkData.Type.PASSENGER; + linkPacket.setEntityLink(new EntityLinkData(session.getPlayerEntity().getGeyserId(), geyserId, type, true, false)); + session.sendUpstreamPacket(linkPacket); + + merchantInventory.setVillager(villager); + } } @Override public void openInventory(GeyserSession session, Inventory inventory) { - + //Handled in JavaTradeListTranslator } @Override public void closeInventory(GeyserSession session, Inventory inventory) { - session.setLastInteractedVillagerEid(-1); - session.setVillagerTrades(null); + MerchantContainer merchantInventory = (MerchantContainer) inventory; + if (merchantInventory.getVillager() != null) { + merchantInventory.getVillager().despawnEntity(session); + } } @Override @@ -115,11 +147,7 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator { } @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - if (actions.stream().anyMatch(a -> a.getSource().getContainerId() == -31)) { - return; - } - - super.translateActions(session, inventory, actions); + public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { + return new MerchantContainer(name, windowId, windowType, this.size, playerInventory); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java index 7d7673c4e..48315fc1c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java @@ -25,18 +25,15 @@ package org.geysermc.connector.network.translators.inventory; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import com.nukkitx.protocol.bedrock.data.inventory.InventorySource; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.protocol.bedrock.data.inventory.*; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; +import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.action.InventoryActionDataTranslator; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.utils.InventoryUtils; import org.geysermc.connector.utils.LanguageUtils; @@ -59,11 +56,11 @@ public class PlayerInventoryTranslator extends InventoryTranslator { ItemData[] contents = new ItemData[36]; // Inventory for (int i = 9; i < 36; i++) { - contents[i] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); + contents[i] = inventory.getItem(i).getItemData(session); } // Hotbar for (int i = 36; i < 45; i++) { - contents[i - 36] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); + contents[i - 36] = inventory.getItem(i).getItemData(session); } inventoryContentPacket.setContents(contents); session.sendUpstreamPacket(inventoryContentPacket); @@ -73,7 +70,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { armorContentPacket.setContainerId(ContainerId.ARMOR); contents = new ItemData[4]; for (int i = 5; i < 9; i++) { - contents[i - 5] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); + contents[i - 5] = inventory.getItem(i).getItemData(session); } armorContentPacket.setContents(contents); session.sendUpstreamPacket(armorContentPacket); @@ -81,7 +78,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { // Offhand InventoryContentPacket offhandPacket = new InventoryContentPacket(); offhandPacket.setContainerId(ContainerId.OFFHAND); - offhandPacket.setContents(new ItemData[]{ItemTranslator.translateToBedrock(session, inventory.getItem(45))}); + offhandPacket.setContents(new ItemData[]{inventory.getItem(45).getItemData(session)}); session.sendUpstreamPacket(offhandPacket); } @@ -100,7 +97,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { if (session.getGameMode() == GameMode.CREATIVE) { slotPacket.setItem(UNUSUABLE_CRAFTING_SPACE_BLOCK); }else{ - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i))); + slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i).getItemStack())); } session.sendUpstreamPacket(slotPacket); @@ -125,21 +122,23 @@ public class PlayerInventoryTranslator extends InventoryTranslator { slotPacket.setContainerId(ContainerId.UI); slotPacket.setSlot(slot + 27); } - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(slot))); + slotPacket.setItem(inventory.getItem(slot).getItemData(session)); session.sendUpstreamPacket(slotPacket); } else if (slot == 45) { InventoryContentPacket offhandPacket = new InventoryContentPacket(); offhandPacket.setContainerId(ContainerId.OFFHAND); - offhandPacket.setContents(new ItemData[]{ItemTranslator.translateToBedrock(session, inventory.getItem(slot))}); + offhandPacket.setContents(new ItemData[]{inventory.getItem(slot).getItemData(session)}); session.sendUpstreamPacket(offhandPacket); } } @Override - public int bedrockSlotToJava(InventoryActionData action) { - int slotnum = action.getSlot(); - switch (action.getSource().getContainerId()) { - case ContainerId.INVENTORY: + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + int slotnum = slotInfoData.getSlot(); + switch (slotInfoData.getContainer()) { + case HOTBAR_AND_INVENTORY: + case HOTBAR: + case INVENTORY: // Inventory if (slotnum >= 9 && slotnum <= 35) { return slotnum; @@ -149,19 +148,19 @@ public class PlayerInventoryTranslator extends InventoryTranslator { return slotnum + 36; } break; - case ContainerId.ARMOR: + case ARMOR: if (slotnum >= 0 && slotnum <= 3) { return slotnum + 5; } break; - case ContainerId.OFFHAND: + case OFFHAND: return 45; - case ContainerId.UI: + case CRAFTING_INPUT: if (slotnum >= 28 && 31 >= slotnum) { return slotnum - 27; } break; - case ContainerId.CRAFTING_RESULT: + case CREATIVE_OUTPUT: return 0; } return slotnum; @@ -169,7 +168,26 @@ public class PlayerInventoryTranslator extends InventoryTranslator { @Override public int javaSlotToBedrock(int slot) { - return slot; + return -1; + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot >= 36 && slot <= 44) { + return new BedrockContainerSlot(ContainerSlotType.HOTBAR, slot - 36); + } else if (slot >= 9 && slot <= 35) { + return new BedrockContainerSlot(ContainerSlotType.INVENTORY, slot); + } else if (slot >= 5 && slot <= 8) { + return new BedrockContainerSlot(ContainerSlotType.ARMOR, slot - 5); + } else if (slot == 45) { + return new BedrockContainerSlot(ContainerSlotType.OFFHAND, 1); + } else if (slot >= 1 && slot <= 4) { + return new BedrockContainerSlot(ContainerSlotType.CRAFTING_INPUT, slot + 27); + } else if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.CRAFTING_OUTPUT, 0); + } else { + throw new IllegalArgumentException("Unknown bedrock slot"); + } } @Override @@ -180,52 +198,13 @@ public class PlayerInventoryTranslator extends InventoryTranslator { } @Override - public void translateActions(GeyserSession session, Inventory inventory, List actions) { - if (session.getGameMode() == GameMode.CREATIVE) { - //crafting grid is not visible in creative mode in java edition - for (InventoryActionData action : actions) { - if (action.getSource().getContainerId() == ContainerId.UI && (action.getSlot() >= 28 && 31 >= action.getSlot())) { - updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - } - } + public void translateRequests(GeyserSession session, Inventory inventory, List requests) { + super.translateRequests(session, inventory, requests); + } - ItemStack javaItem; - for (InventoryActionData action : actions) { - switch (action.getSource().getContainerId()) { - case ContainerId.INVENTORY: - case ContainerId.ARMOR: - case ContainerId.OFFHAND: - int javaSlot = bedrockSlotToJava(action); - if (action.getToItem().getId() == 0) { - javaItem = new ItemStack(-1, 0, null); - } else { - javaItem = ItemTranslator.translateToJava(action.getToItem()); - } - ClientCreativeInventoryActionPacket creativePacket = new ClientCreativeInventoryActionPacket(javaSlot, javaItem); - session.sendDownstreamPacket(creativePacket); - inventory.setItem(javaSlot, javaItem); - break; - case ContainerId.UI: - if (action.getSlot() == 0) { - session.getInventory().setCursor(ItemTranslator.translateToJava(action.getToItem())); - } - break; - case ContainerId.NONE: - if (action.getSource().getType() == InventorySource.Type.WORLD_INTERACTION - && action.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) { - javaItem = ItemTranslator.translateToJava(action.getToItem()); - ClientCreativeInventoryActionPacket creativeDropPacket = new ClientCreativeInventoryActionPacket(-1, javaItem); - session.sendDownstreamPacket(creativeDropPacket); - } - break; - } - } - return; - } - - InventoryActionDataTranslator.translate(this, session, inventory, actions); + @Override + public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { + throw new UnsupportedOperationException(); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SmithingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SmithingInventoryTranslator.java deleted file mode 100644 index f7f0acd8c..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SmithingInventoryTranslator.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory; - -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; - -public class SmithingInventoryTranslator extends BlockInventoryTranslator { - - public SmithingInventoryTranslator() { - super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, new CursorInventoryUpdater()); - } - - @Override - public int bedrockSlotToJava(InventoryActionData action) { - final int slot = super.bedrockSlotToJava(action); - if (action.getSource().getContainerId() == ContainerId.UI) { - switch (slot) { - case 51: - return 0; - case 52: - return 1; - case 50: - return 2; - default: - return slot; - } - } return slot; - } - - @Override - public int javaSlotToBedrock(int slot) { - switch (slot) { - case 0: - return 51; - case 1: - return 52; - case 2: - return 50; - } - return super.javaSlotToBedrock(slot); - } - -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java deleted file mode 100644 index a9c1eddca..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory.action; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.window.WindowAction; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.inventory.PlayerInventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.inventory.SlotType; -import org.geysermc.connector.utils.InventoryUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.ListIterator; - -class ClickPlan { - private final List plan = new ArrayList<>(); - - public void add(Click click, int slot) { - plan.add(new ClickAction(click, slot)); - } - - public void execute(GeyserSession session, InventoryTranslator translator, Inventory inventory, boolean refresh) { - PlayerInventory playerInventory = session.getInventory(); - ListIterator planIter = plan.listIterator(); - while (planIter.hasNext()) { - final ClickAction action = planIter.next(); - final ItemStack cursorItem = playerInventory.getCursor(); - final ItemStack clickedItem = inventory.getItem(action.slot); - final short actionId = (short) inventory.getTransactionId().getAndIncrement(); - - //TODO: stop relying on refreshing the inventory for crafting to work properly - if (translator.getSlotType(action.slot) != SlotType.NORMAL) - refresh = true; - - ClientWindowActionPacket clickPacket = new ClientWindowActionPacket(inventory.getId(), - actionId, action.slot, !planIter.hasNext() && refresh ? InventoryUtils.REFRESH_ITEM : clickedItem, - WindowAction.CLICK_ITEM, action.click.actionParam); - - if (translator.getSlotType(action.slot) == SlotType.OUTPUT) { - if (cursorItem == null && clickedItem != null) { - playerInventory.setCursor(clickedItem); - } else if (InventoryUtils.canStack(cursorItem, clickedItem)) { - playerInventory.setCursor(new ItemStack(cursorItem.getId(), - cursorItem.getAmount() + clickedItem.getAmount(), cursorItem.getNbt())); - } - } else { - switch (action.click) { - case LEFT: - if (!InventoryUtils.canStack(cursorItem, clickedItem)) { - playerInventory.setCursor(clickedItem); - inventory.setItem(action.slot, cursorItem); - } else { - playerInventory.setCursor(null); - inventory.setItem(action.slot, new ItemStack(clickedItem.getId(), - clickedItem.getAmount() + cursorItem.getAmount(), clickedItem.getNbt())); - } - break; - case RIGHT: - if (cursorItem == null && clickedItem != null) { - ItemStack halfItem = new ItemStack(clickedItem.getId(), - clickedItem.getAmount() / 2, clickedItem.getNbt()); - inventory.setItem(action.slot, halfItem); - playerInventory.setCursor(new ItemStack(clickedItem.getId(), - clickedItem.getAmount() - halfItem.getAmount(), clickedItem.getNbt())); - } else if (cursorItem != null && clickedItem == null) { - playerInventory.setCursor(new ItemStack(cursorItem.getId(), - cursorItem.getAmount() - 1, cursorItem.getNbt())); - inventory.setItem(action.slot, new ItemStack(cursorItem.getId(), - 1, cursorItem.getNbt())); - } else if (InventoryUtils.canStack(cursorItem, clickedItem)) { - playerInventory.setCursor(new ItemStack(cursorItem.getId(), - cursorItem.getAmount() - 1, cursorItem.getNbt())); - inventory.setItem(action.slot, new ItemStack(clickedItem.getId(), - clickedItem.getAmount() + 1, clickedItem.getNbt())); - } - break; - } - } - session.sendDownstreamPacket(clickPacket); - session.sendDownstreamPacket(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true)); - } - - /*if (refresh) { - translator.updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - }*/ - } - - private static class ClickAction { - final Click click; - final int slot; - ClickAction(Click click, int slot) { - this.click = click; - this.slot = slot; - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java deleted file mode 100644 index 96cbd61fb..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory.action; - -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; -import com.github.steveice10.mc.protocol.data.game.window.*; -import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.data.inventory.InventoryActionData; -import com.nukkitx.protocol.bedrock.data.inventory.InventorySource; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.inventory.SlotType; -import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.utils.InventoryUtils; - -import java.util.*; - -public class InventoryActionDataTranslator { - public static void translate(InventoryTranslator translator, GeyserSession session, Inventory inventory, List actions) { - if (actions.size() != 2) - return; - - InventoryActionData worldAction = null; - InventoryActionData cursorAction = null; - InventoryActionData containerAction = null; - boolean refresh = false; - for (InventoryActionData action : actions) { - if (action.getSource().getContainerId() == ContainerId.CRAFTING_USE_INGREDIENT) { - return; - } else if (action.getSource().getType() == InventorySource.Type.WORLD_INTERACTION) { - worldAction = action; - } else if (action.getSource().getContainerId() == ContainerId.UI && action.getSlot() == 0) { - cursorAction = action; - ItemData translatedCursor = ItemTranslator.translateToBedrock(session, session.getInventory().getCursor()); - if (!translatedCursor.equals(action.getFromItem())) { - refresh = true; - } - } else { - containerAction = action; - ItemData translatedItem = ItemTranslator.translateToBedrock(session, inventory.getItem(translator.bedrockSlotToJava(action))); - if (!translatedItem.equals(action.getFromItem())) { - refresh = true; - } - } - } - - final int craftSlot = session.getCraftSlot(); - session.setCraftSlot(0); - - if (worldAction != null) { - InventoryActionData sourceAction; - if (cursorAction != null) { - sourceAction = cursorAction; - } else { - sourceAction = containerAction; - } - - if (sourceAction != null) { - if (worldAction.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) { - //quick dropping from hotbar? - if (session.getInventoryCache().getOpenInventory() == null && sourceAction.getSource().getContainerId() == ContainerId.INVENTORY) { - int heldSlot = session.getInventory().getHeldItemSlot(); - if (sourceAction.getSlot() == heldSlot) { - ClientPlayerActionPacket actionPacket = new ClientPlayerActionPacket( - sourceAction.getToItem().getCount() == 0 ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM, - new Position(0, 0, 0), BlockFace.DOWN); - session.sendDownstreamPacket(actionPacket); - ItemStack item = session.getInventory().getItem(heldSlot); - if (item != null) { - session.getInventory().setItem(heldSlot, new ItemStack(item.getId(), item.getAmount() - 1, item.getNbt())); - } - return; - } - } - int dropAmount = sourceAction.getFromItem().getCount() - sourceAction.getToItem().getCount(); - if (sourceAction != cursorAction) { //dropping directly from inventory - int javaSlot = translator.bedrockSlotToJava(sourceAction); - if (dropAmount == sourceAction.getFromItem().getCount()) { - ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(), - inventory.getTransactionId().getAndIncrement(), - javaSlot, null, WindowAction.DROP_ITEM, - DropItemParam.DROP_SELECTED_STACK); - session.sendDownstreamPacket(dropPacket); - } else { - for (int i = 0; i < dropAmount; i++) { - ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(), - inventory.getTransactionId().getAndIncrement(), - javaSlot, null, WindowAction.DROP_ITEM, - DropItemParam.DROP_FROM_SELECTED); - session.sendDownstreamPacket(dropPacket); - } - } - ItemStack item = inventory.getItem(javaSlot); - if (item != null) { - inventory.setItem(javaSlot, new ItemStack(item.getId(), item.getAmount() - dropAmount, item.getNbt())); - } - return; - } else { //clicking outside of inventory - ClientWindowActionPacket dropPacket = new ClientWindowActionPacket(inventory.getId(), inventory.getTransactionId().getAndIncrement(), - -999, null, WindowAction.CLICK_ITEM, - dropAmount > 1 ? ClickItemParam.LEFT_CLICK : ClickItemParam.RIGHT_CLICK); - session.sendDownstreamPacket(dropPacket); - ItemStack cursor = session.getInventory().getCursor(); - if (cursor != null) { - session.getInventory().setCursor(new ItemStack(cursor.getId(), dropAmount > 1 ? 0 : cursor.getAmount() - 1, cursor.getNbt())); - } - return; - } - } - } - } else if (cursorAction != null && containerAction != null) { - //left/right click - ClickPlan plan = new ClickPlan(); - int javaSlot = translator.bedrockSlotToJava(containerAction); - if (cursorAction.getFromItem().equals(containerAction.getToItem()) - && containerAction.getFromItem().equals(cursorAction.getToItem()) - && !InventoryUtils.canStack(cursorAction.getFromItem(), containerAction.getFromItem())) { //simple swap - plan.add(Click.LEFT, javaSlot); - } else if (cursorAction.getFromItem().getCount() > cursorAction.getToItem().getCount()) { //release - if (cursorAction.getToItem().getCount() == 0) { - plan.add(Click.LEFT, javaSlot); - } else { - int difference = cursorAction.getFromItem().getCount() - cursorAction.getToItem().getCount(); - for (int i = 0; i < difference; i++) { - plan.add(Click.RIGHT, javaSlot); - } - } - } else { //pickup - if (cursorAction.getFromItem().getCount() == 0) { - if (containerAction.getToItem().getCount() == 0) { //pickup all - plan.add(Click.LEFT, javaSlot); - } else { //pickup some - if (translator.getSlotType(javaSlot) == SlotType.FURNACE_OUTPUT - || containerAction.getToItem().getCount() == containerAction.getFromItem().getCount() / 2) { //right click - plan.add(Click.RIGHT, javaSlot); - } else { - plan.add(Click.LEFT, javaSlot); - int difference = containerAction.getFromItem().getCount() - cursorAction.getToItem().getCount(); - for (int i = 0; i < difference; i++) { - plan.add(Click.RIGHT, javaSlot); - } - } - } - } else { //pickup into non-empty cursor - if (translator.getSlotType(javaSlot) == SlotType.FURNACE_OUTPUT) { - if (containerAction.getToItem().getCount() == 0) { - plan.add(Click.LEFT, javaSlot); - } else { - ClientWindowActionPacket shiftClickPacket = new ClientWindowActionPacket(inventory.getId(), - inventory.getTransactionId().getAndIncrement(), - javaSlot, InventoryUtils.REFRESH_ITEM, WindowAction.SHIFT_CLICK_ITEM, - ShiftClickItemParam.LEFT_CLICK); - session.sendDownstreamPacket(shiftClickPacket); - translator.updateInventory(session, inventory); - return; - } - } else if (translator.getSlotType(javaSlot) == SlotType.OUTPUT) { - plan.add(Click.LEFT, javaSlot); - } else { - int cursorSlot = findTempSlot(inventory, session.getInventory().getCursor(), Collections.singletonList(javaSlot), false); - if (cursorSlot != -1) { - plan.add(Click.LEFT, cursorSlot); - } else { - translator.updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - } - plan.add(Click.LEFT, javaSlot); - int difference = cursorAction.getToItem().getCount() - cursorAction.getFromItem().getCount(); - for (int i = 0; i < difference; i++) { - plan.add(Click.RIGHT, cursorSlot); - } - plan.add(Click.LEFT, javaSlot); - plan.add(Click.LEFT, cursorSlot); - } - } - } - plan.execute(session, translator, inventory, refresh); - return; - } else { - ClickPlan plan = new ClickPlan(); - InventoryActionData fromAction; - InventoryActionData toAction; - if (actions.get(0).getFromItem().getCount() >= actions.get(0).getToItem().getCount()) { - fromAction = actions.get(0); - toAction = actions.get(1); - } else { - fromAction = actions.get(1); - toAction = actions.get(0); - } - int fromSlot = translator.bedrockSlotToJava(fromAction); - int toSlot = translator.bedrockSlotToJava(toAction); - - if (translator.getSlotType(fromSlot) == SlotType.OUTPUT) { - if ((craftSlot != 0 && craftSlot != -2) && (inventory.getItem(toSlot) == null - || InventoryUtils.canStack(session.getInventory().getCursor(), inventory.getItem(toSlot)))) { - if (fromAction.getToItem().getCount() == 0) { - refresh = true; - plan.add(Click.LEFT, toSlot); - if (craftSlot != -1) { - plan.add(Click.LEFT, craftSlot); - } - } else { - int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount(); - for (int i = 0; i < difference; i++) { - plan.add(Click.RIGHT, toSlot); - } - session.setCraftSlot(craftSlot); - } - plan.execute(session, translator, inventory, refresh); - return; - } else { - session.setCraftSlot(-2); - } - } - - int cursorSlot = -1; - if (session.getInventory().getCursor() != null) { //move cursor contents to a temporary slot - cursorSlot = findTempSlot(inventory, - session.getInventory().getCursor(), - Arrays.asList(fromSlot, toSlot), - translator.getSlotType(fromSlot) == SlotType.OUTPUT); - if (cursorSlot != -1) { - plan.add(Click.LEFT, cursorSlot); - } else { - translator.updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - return; - } - } - if ((fromAction.getFromItem().equals(toAction.getToItem()) && !InventoryUtils.canStack(fromAction.getFromItem(), toAction.getFromItem())) - || fromAction.getToItem().getId() == 0) { //slot swap - plan.add(Click.LEFT, fromSlot); - plan.add(Click.LEFT, toSlot); - if (fromAction.getToItem().getId() != 0) { - plan.add(Click.LEFT, fromSlot); - } - } else if (InventoryUtils.canStack(fromAction.getFromItem(), toAction.getToItem())) { //partial item move - if (translator.getSlotType(fromSlot) == SlotType.FURNACE_OUTPUT) { - ClientWindowActionPacket shiftClickPacket = new ClientWindowActionPacket(inventory.getId(), - inventory.getTransactionId().getAndIncrement(), - fromSlot, InventoryUtils.REFRESH_ITEM, WindowAction.SHIFT_CLICK_ITEM, - ShiftClickItemParam.LEFT_CLICK); - session.sendDownstreamPacket(shiftClickPacket); - translator.updateInventory(session, inventory); - return; - } else if (translator.getSlotType(fromSlot) == SlotType.OUTPUT) { - session.setCraftSlot(cursorSlot); - plan.add(Click.LEFT, fromSlot); - int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount(); - for (int i = 0; i < difference; i++) { - plan.add(Click.RIGHT, toSlot); - } - //client will send additional packets later to finish transferring crafting output - //translator will know how to handle this using the craftSlot variable - } else { - plan.add(Click.LEFT, fromSlot); - int difference = toAction.getToItem().getCount() - toAction.getFromItem().getCount(); - for (int i = 0; i < difference; i++) { - plan.add(Click.RIGHT, toSlot); - } - plan.add(Click.LEFT, fromSlot); - } - } - if (cursorSlot != -1) { - plan.add(Click.LEFT, cursorSlot); - } - plan.execute(session, translator, inventory, refresh); - return; - } - - translator.updateInventory(session, inventory); - InventoryUtils.updateCursor(session); - } - - private static int findTempSlot(Inventory inventory, ItemStack item, List slotBlacklist, boolean emptyOnly) { - /*try and find a slot that can temporarily store the given item - only look in the main inventory and hotbar - only slots that are empty or contain a different type of item are valid*/ - int offset = inventory.getId() == 0 ? 1 : 0; //offhand is not a viable slot (some servers disable it) - List itemBlacklist = new ArrayList<>(slotBlacklist.size() + 1); - itemBlacklist.add(item); - for (int slot : slotBlacklist) { - ItemStack blacklistItem = inventory.getItem(slot); - if (blacklistItem != null) - itemBlacklist.add(blacklistItem); - } - for (int i = inventory.getSize() - (36 + offset); i < inventory.getSize() - offset; i++) { - ItemStack testItem = inventory.getItem(i); - boolean acceptable = true; - if (testItem != null) { - if (emptyOnly) { - continue; - } - for (ItemStack blacklistItem : itemBlacklist) { - if (InventoryUtils.canStack(testItem, blacklistItem)) { - acceptable = false; - break; - } - } - } - if (acceptable && !slotBlacklist.contains(i)) - return i; - } - //could not find a viable temp slot - return -1; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/Click.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java similarity index 66% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/Click.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java index 1fdfa3640..d27290bff 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/Click.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java @@ -23,16 +23,25 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory.action; +package org.geysermc.connector.network.translators.inventory.click; import com.github.steveice10.mc.protocol.data.game.window.ClickItemParam; +import com.github.steveice10.mc.protocol.data.game.window.DropItemParam; +import com.github.steveice10.mc.protocol.data.game.window.WindowAction; import com.github.steveice10.mc.protocol.data.game.window.WindowActionParam; import lombok.AllArgsConstructor; @AllArgsConstructor -enum Click { - LEFT(ClickItemParam.LEFT_CLICK), - RIGHT(ClickItemParam.RIGHT_CLICK); +public enum Click { + LEFT(WindowAction.CLICK_ITEM, ClickItemParam.LEFT_CLICK), + RIGHT(WindowAction.CLICK_ITEM, ClickItemParam.RIGHT_CLICK), + DROP_ONE(WindowAction.DROP_ITEM, DropItemParam.DROP_FROM_SELECTED), + DROP_ALL(WindowAction.DROP_ITEM, DropItemParam.DROP_SELECTED_STACK), + LEFT_OUTSIDE(WindowAction.CLICK_ITEM, ClickItemParam.LEFT_CLICK), + RIGHT_OUTSIDE(WindowAction.CLICK_ITEM, ClickItemParam.RIGHT_CLICK); + public static final int OUTSIDE_SLOT = -999; + + public final WindowAction windowAction; public final WindowActionParam actionParam; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java new file mode 100644 index 000000000..f4cf35617 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.click; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.window.WindowAction; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import lombok.Value; +import org.geysermc.connector.inventory.GeyserItemStack; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.inventory.SlotType; +import org.geysermc.connector.utils.InventoryUtils; + +import java.util.*; + +public class ClickPlan { + private final List plan = new ArrayList<>(); + private final Int2ObjectMap simulatedItems; + private GeyserItemStack simulatedCursor; + private boolean simulating; + + private final GeyserSession session; + private final InventoryTranslator translator; + private final Inventory inventory; + + public ClickPlan(GeyserSession session, InventoryTranslator translator, Inventory inventory) { + this.session = session; + this.translator = translator; + this.inventory = inventory; + + this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize()); + this.simulatedCursor = session.getPlayerInventory().getCursor().copy(); + this.simulating = true; + } + + public void add(Click click, int slot) { + if (!simulating) + throw new UnsupportedOperationException("ClickPlan already executed"); + + if (click == Click.LEFT_OUTSIDE || click == Click.RIGHT_OUTSIDE) { + slot = Click.OUTSIDE_SLOT; + } + + ClickAction action = new ClickAction(click, slot); + plan.add(action); + simulateAction(action); + } + + public void execute(boolean refresh) { + simulating = false; + ListIterator planIter = plan.listIterator(); + while (planIter.hasNext()) { + ClickAction action = planIter.next(); + + if (action.slot != Click.OUTSIDE_SLOT && translator.getSlotType(action.slot) != SlotType.NORMAL) { + refresh = true; + } + + ItemStack clickedItemStack; + if (!planIter.hasNext() && refresh) { + clickedItemStack = InventoryUtils.REFRESH_ITEM; + } else if (action.click.windowAction == WindowAction.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) { + clickedItemStack = null; + } else { + clickedItemStack = inventory.getItem(action.slot).getItemStack(); + } + + short actionId = inventory.getNextTransactionId(); + ClientWindowActionPacket clickPacket = new ClientWindowActionPacket( + inventory.getId(), + actionId, + action.slot, + clickedItemStack, + action.click.windowAction, + action.click.actionParam + ); + + simulateAction(action); + + session.sendDownstreamPacket(clickPacket); + if (clickedItemStack == InventoryUtils.REFRESH_ITEM) { + session.sendDownstreamPacket(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true)); + } + System.out.println(clickPacket); + } + } + + public GeyserItemStack getItem(int slot) { + return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy()); + } + + public GeyserItemStack getCursor() { + return simulatedCursor; + } + + private void setItem(int slot, GeyserItemStack item) { + if (simulating) { + simulatedItems.put(slot, item); + } else { + inventory.setItem(slot, item); + } + } + + private void setCursor(GeyserItemStack item) { + if (simulating) { + simulatedCursor = item; + } else { + session.getPlayerInventory().setCursor(item); + } + } + + private void simulateAction(ClickAction action) { + GeyserItemStack cursor = simulating ? getCursor() : session.getPlayerInventory().getCursor(); + switch (action.click) { + case LEFT_OUTSIDE: + setCursor(GeyserItemStack.EMPTY); + return; + case RIGHT_OUTSIDE: + if (!cursor.isEmpty()) { + cursor.sub(1); + } + return; + } + + GeyserItemStack clicked = simulating ? getItem(action.slot) : inventory.getItem(action.slot); + if (translator.getSlotType(action.slot) == SlotType.OUTPUT) { + if (cursor.isEmpty() && !clicked.isEmpty()) { + setCursor(clicked.copy()); + } else if (InventoryUtils.canStack(cursor, clicked)) { + cursor.add(clicked.getAmount()); + } + } else { + switch (action.click) { + case LEFT: + if (!InventoryUtils.canStack(cursor, clicked)) { + setCursor(clicked); + setItem(action.slot, cursor); + } else { + setCursor(GeyserItemStack.EMPTY); + clicked.add(cursor.getAmount()); + } + break; + case RIGHT: + if (cursor.isEmpty() && !clicked.isEmpty()) { + int half = clicked.getAmount() / 2; //smaller half + setCursor(clicked.copy(clicked.getAmount() - half)); //larger half + clicked.setAmount(half); + } else if (!cursor.isEmpty() && clicked.isEmpty()) { + cursor.sub(1); + setItem(action.slot, cursor.copy(1)); + } else if (InventoryUtils.canStack(cursor, clicked)) { + cursor.sub(1); + clicked.add(1); + } + break; + case DROP_ONE: + if (!clicked.isEmpty()) { + clicked.sub(1); + } + break; + case DROP_ALL: + setItem(action.slot, GeyserItemStack.EMPTY); + break; + } + } + } + + public Set getAffectedSlots() { + Set affectedSlots = new HashSet<>(); + for (ClickAction action : plan) { + if (translator.getSlotType(action.slot) == SlotType.NORMAL && action.slot != Click.OUTSIDE_SLOT) { + affectedSlots.add(action.slot); + } + } + return affectedSlots; + } + + @Value + private static class ClickAction { + Click click; + int slot; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java index e45945bc3..58e6178b4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java @@ -49,7 +49,7 @@ public class ChestInventoryUpdater extends InventoryUpdater { ItemData[] bedrockItems = new ItemData[paddedSize]; for (int i = 0; i < bedrockItems.length; i++) { if (i < translator.size) { - bedrockItems[i] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); + bedrockItems[i] = inventory.getItem(i).getItemData(session); } else { bedrockItems[i] = UNUSUABLE_SPACE_BLOCK; } @@ -69,7 +69,7 @@ public class ChestInventoryUpdater extends InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(inventory.getId()); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot))); + slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); session.sendUpstreamPacket(slotPacket); return true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java index 51f33ac43..1abc65b5b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.java @@ -40,7 +40,7 @@ public class ContainerInventoryUpdater extends InventoryUpdater { ItemData[] bedrockItems = new ItemData[translator.size]; for (int i = 0; i < bedrockItems.length; i++) { - bedrockItems[translator.javaSlotToBedrock(i)] = ItemTranslator.translateToBedrock(session, inventory.getItem(i)); + bedrockItems[translator.javaSlotToBedrock(i)] = inventory.getItem(i).getItemData(session); } InventoryContentPacket contentPacket = new InventoryContentPacket(); @@ -57,7 +57,7 @@ public class ContainerInventoryUpdater extends InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(inventory.getId()); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot))); + slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); session.sendUpstreamPacket(slotPacket); return true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java index 57601a9e0..655868c9a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java @@ -39,7 +39,7 @@ public abstract class InventoryUpdater { ItemData[] bedrockItems = new ItemData[36]; for (int i = 0; i < 36; i++) { final int offset = i < 9 ? 27 : -9; - bedrockItems[i] = ItemTranslator.translateToBedrock(session, inventory.getItem(translator.size + i + offset)); + bedrockItems[i] = inventory.getItem(translator.size + i + offset).getItemData(session); } InventoryContentPacket contentPacket = new InventoryContentPacket(); contentPacket.setContainerId(ContainerId.INVENTORY); @@ -52,7 +52,7 @@ public abstract class InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(ContainerId.INVENTORY); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot))); + slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); session.sendUpstreamPacket(slotPacket); return true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/UIInventoryUpdater.java similarity index 89% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/UIInventoryUpdater.java index 26d889900..5100ddc99 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/UIInventoryUpdater.java @@ -32,9 +32,8 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.item.ItemTranslator; -public class CursorInventoryUpdater extends InventoryUpdater { +public class UIInventoryUpdater extends InventoryUpdater { - //TODO: Consider renaming this? Since the Protocol enum updated @Override public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { super.updateInventory(translator, session, inventory); @@ -46,7 +45,7 @@ public class CursorInventoryUpdater extends InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(ContainerId.UI); slotPacket.setSlot(bedrockSlot); - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i))); + slotPacket.setItem(inventory.getItem(i).getItemData(session)); session.sendUpstreamPacket(slotPacket); } } @@ -59,7 +58,7 @@ public class CursorInventoryUpdater extends InventoryUpdater { InventorySlotPacket slotPacket = new InventorySlotPacket(); slotPacket.setContainerId(ContainerId.UI); slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); - slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(javaSlot))); + slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); session.sendUpstreamPacket(slotPacket); return true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index 9ffb4f0d9..559da5d97 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -34,6 +34,8 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import lombok.AllArgsConstructor; @@ -51,8 +53,10 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator recipeMap = new Int2ObjectOpenHashMap<>(); CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); craftingDataPacket.setCleanRecipes(true); + int netId = 1; for (Recipe recipe : packet.getRecipes()) { switch (recipe.getType()) { case CRAFTING_SHAPELESS: { @@ -63,7 +67,8 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator // Max health must be divisible by two in bedrock entity.getAttributes().put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(maxHealth, (maxHealth % 2 == 1 ? maxHealth + 1 : maxHealth))); - session.getInventoryCache().setOpenInventory(null); + session.setOpenInventory(null); SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); playerGameTypePacket.setGamemode(packet.getGamemode().ordinal()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java index 9ed11a23d..05cc1a418 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerActionAckTranslator.java @@ -32,6 +32,7 @@ import com.github.steveice10.opennbt.tag.builtin.*; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -71,15 +72,9 @@ public class JavaPlayerActionAckTranslator extends PacketTranslator { + PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket(); + hotbarPacket.setContainerId(0); + hotbarPacket.setSelectedHotbarSlot(packet.getSlot()); + hotbarPacket.setSelectHotbarSlot(true); + session.sendUpstreamPacket(hotbarPacket); - session.getInventory().setHeldItemSlot(packet.getSlot()); + session.getPlayerInventory().setHeldItemSlot(packet.getSlot()); + }); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java index 93cfa08e4..68f1dda91 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.connector.network.translators.java.window; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerCloseWindowPacket; +import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -36,7 +37,8 @@ public class JavaCloseWindowTranslator extends PacketTranslator { + InventoryUtils.closeInventory(session, packet.getWindowId()); + }); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java index e67877536..6d6c1686c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java @@ -27,6 +27,7 @@ package org.geysermc.connector.network.translators.java.window; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfirmTransactionPacket; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerConfirmTransactionPacket; +import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -36,9 +37,11 @@ public class JavaConfirmTransactionTranslator extends PacketTranslator { + if (!packet.isAccepted()) { + ClientConfirmTransactionPacket confirmPacket = new ClientConfirmTransactionPacket(packet.getWindowId(), packet.getActionId(), true); + session.sendDownstreamPacket(confirmPacket); + } + }); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 820639b3d..e5ef4ecf5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -43,46 +43,47 @@ public class JavaOpenWindowTranslator extends PacketTranslator { + if (packet.getWindowId() == 0) { + return; + } + + InventoryTranslator newTranslator = InventoryTranslator.INVENTORY_TRANSLATORS.get(packet.getType()); + Inventory openInventory = session.getOpenInventory(); + //No translator exists for this window type. Close all windows and return. + if (newTranslator == null) { + if (openInventory != null) { + InventoryUtils.closeInventory(session, openInventory.getId()); + } + ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(packet.getWindowId()); + session.sendDownstreamPacket(closeWindowPacket); + return; + } + + String name = packet.getName(); + try { + JsonParser parser = new JsonParser(); + JsonObject jsonObject = parser.parse(packet.getName()).getAsJsonObject(); + if (jsonObject.has("text")) { + name = jsonObject.get("text").getAsString(); + } else if (jsonObject.has("translate")) { + name = jsonObject.get("translate").getAsString(); + } + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().debug("JavaOpenWindowTranslator: " + e.toString()); + } + + name = LocaleUtils.getLocaleString(name, session.getClientData().getLanguageCode()); + + Inventory newInventory = newTranslator.createInventory(name, packet.getWindowId(), packet.getType(), session.getPlayerInventory()); if (openInventory != null) { - InventoryUtils.closeWindow(session, openInventory.getId()); - InventoryUtils.closeInventory(session, openInventory.getId()); + InventoryTranslator openTranslator = InventoryTranslator.INVENTORY_TRANSLATORS.get(openInventory.getWindowType()); + if (!openTranslator.getClass().equals(newTranslator.getClass())) { + InventoryUtils.closeInventory(session, openInventory.getId()); + } } - ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(packet.getWindowId()); - session.sendDownstreamPacket(closeWindowPacket); - return; - } - String name = packet.getName(); - try { - JsonParser parser = new JsonParser(); - JsonObject jsonObject = parser.parse(packet.getName()).getAsJsonObject(); - if (jsonObject.has("text")) { - name = jsonObject.get("text").getAsString(); - } else if (jsonObject.has("translate")) { - name = jsonObject.get("translate").getAsString(); - } - } catch (Exception e) { - GeyserConnector.getInstance().getLogger().debug("JavaOpenWindowTranslator: " + e.toString()); - } - - name = LocaleUtils.getLocaleString(name, session.getClientData().getLanguageCode()); - - Inventory newInventory = new Inventory(name, packet.getWindowId(), packet.getType(), newTranslator.size + 36); - session.getInventoryCache().cacheInventory(newInventory); - if (openInventory != null) { - InventoryTranslator openTranslator = InventoryTranslator.INVENTORY_TRANSLATORS.get(openInventory.getWindowType()); - if (!openTranslator.getClass().equals(newTranslator.getClass())) { - InventoryUtils.closeWindow(session, openInventory.getId()); - InventoryUtils.closeInventory(session, openInventory.getId()); - } - } - - InventoryUtils.openInventory(session, newInventory); + InventoryUtils.openInventory(session, newInventory); + }); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java index 19d7db217..22a0edcac 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java @@ -26,37 +26,53 @@ package org.geysermc.connector.network.translators.java.window; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerSetSlotPacket; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; -import java.util.Objects; - @Translator(packet = ServerSetSlotPacket.class) public class JavaSetSlotTranslator extends PacketTranslator { @Override public void translate(ServerSetSlotPacket packet, GeyserSession session) { - if (packet.getWindowId() == 255 && packet.getSlot() == -1) { //cursor - if (session.getCraftSlot() != 0) + session.addInventoryTask(() -> { + if (packet.getWindowId() == 255) { //cursor + GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); + GeyserItemStack oldItem = session.getPlayerInventory().getCursor(); + if (newItem.getItemData(session).equals(oldItem.getItemData(session))) { + newItem.setNetId(oldItem.getNetId()); + } else { + newItem.setNetId(session.getItemNetId().getAndIncrement()); + } + session.getPlayerInventory().setCursor(newItem); + InventoryUtils.updateCursor(session); + return; + } + + //TODO: support window id -2, should update player inventory + Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId()); + if (inventory == null) return; - session.getInventory().setCursor(packet.getItem()); - InventoryUtils.updateCursor(session); - return; - } - - Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId()); - if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null)) - return; - - InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); - if (translator != null) { - inventory.setItem(packet.getSlot(), packet.getItem()); - translator.updateSlot(session, inventory, packet.getSlot()); - } + InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); + if (translator != null) { + GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); + GeyserItemStack oldItem = inventory.getItem(packet.getSlot()); + if (newItem.getItemData(session).equals(oldItem.getItemData(session), false, false, false)) { + newItem.setNetId(oldItem.getNetId()); + System.out.println("OLD: " + newItem.getNetId()); + } else { + newItem.setNetId(session.getItemNetId().getAndIncrement()); + System.out.println("NEW: " + newItem.getNetId()); + } + inventory.setItem(packet.getSlot(), newItem); + translator.updateSlot(session, inventory, packet.getSlot()); + } + }); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java index 2cc392f53..26aab47b3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java @@ -26,32 +26,40 @@ package org.geysermc.connector.network.translators.java.window; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerWindowItemsPacket; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; - -import java.util.Arrays; +import org.geysermc.connector.utils.InventoryUtils; @Translator(packet = ServerWindowItemsPacket.class) public class JavaWindowItemsTranslator extends PacketTranslator { @Override public void translate(ServerWindowItemsPacket packet, GeyserSession session) { - Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId()); - if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null)) - return; + session.addInventoryTask(() -> { + Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId()); + if (inventory == null) + return; - if (packet.getItems().length < inventory.getSize()) { - inventory.setItems(Arrays.copyOf(packet.getItems(), inventory.getSize())); - } else { - inventory.setItems(packet.getItems()); - } + for (int i = 0; i < packet.getItems().length; i++) { + GeyserItemStack newItem = GeyserItemStack.from(packet.getItems()[i]); + GeyserItemStack oldItem = inventory.getItem(i); + if (newItem.getItemData(session).equals(oldItem.getItemData(session), false, false, false)) { + newItem.setNetId(oldItem.getNetId()); + } else { + newItem.setNetId(session.getItemNetId().getAndIncrement()); + } + inventory.setItem(i, newItem); + } - InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); - if (translator != null) { - translator.updateInventory(session, inventory); - } + InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); + if (translator != null) { + translator.updateInventory(session, inventory); + } + }); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java index daebed1b6..97c4708ff 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java @@ -31,19 +31,22 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.utils.InventoryUtils; @Translator(packet = ServerWindowPropertyPacket.class) public class JavaWindowPropertyTranslator extends PacketTranslator { @Override public void translate(ServerWindowPropertyPacket packet, GeyserSession session) { - Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId()); - if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null)) - return; + session.addInventoryTask(() -> { + Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId()); + if (inventory == null) + return; - InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); - if (translator != null) { - translator.updateProperty(session, inventory, packet.getRawProperty(), packet.getValue()); - } + InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); + if (translator != null) { + translator.updateProperty(session, inventory, packet.getRawProperty(), packet.getValue()); + } + }); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java index d7961dd98..cb1aeaa71 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java @@ -111,7 +111,7 @@ public class JavaNotifyClientTranslator extends PacketTranslator tags = new ArrayList<>(); - for (VillagerTrade trade : packet.getTrades()) { + for (int i = 0; i < packet.getTrades().length; i++) { + VillagerTrade trade = packet.getTrades()[i]; NbtMapBuilder recipe = NbtMap.builder(); - recipe.putInt("maxUses", trade.getMaxUses()); + recipe.putInt("netId", i + 1); + recipe.putInt("maxUses", trade.isTradeDisabled() ? 0 : trade.getMaxUses()); recipe.putInt("traderExp", trade.getXp()); recipe.putFloat("priceMultiplierA", trade.getPriceMultiplier()); recipe.put("sell", getItemTag(session, trade.getOutput(), 0)); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java b/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java index f3dff0cc2..ab73e6028 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/sound/BlockSoundInteractionHandler.java @@ -27,6 +27,7 @@ package org.geysermc.connector.network.translators.sound; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.nukkitx.math.vector.Vector3f; +import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemRegistry; @@ -60,12 +61,12 @@ public interface BlockSoundInteractionHandler extends SoundInteractionHandler 500) { - ContainerClosePacket closePacket = new ContainerClosePacket(); - closePacket.setId((byte) windowId); - session.sendUpstreamPacket(closePacket); + Inventory inventory = getInventory(session, windowId); + if (inventory != null) { + InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); + translator.closeInventory(session, inventory); session.setLastWindowCloseTime(System.currentTimeMillis()); } - */ + session.setOpenInventory(null); + } + + public static Inventory getInventory(GeyserSession session, int windowId) { + if (windowId == 0) { + return session.getPlayerInventory(); + } else { + Inventory openInventory = session.getOpenInventory(); + if (openInventory != null && windowId == openInventory.getId()) { + return openInventory; + } + return null; + } } public static void updateCursor(GeyserSession session) { InventorySlotPacket cursorPacket = new InventorySlotPacket(); cursorPacket.setContainerId(ContainerId.UI); cursorPacket.setSlot(0); - cursorPacket.setItem(ItemTranslator.translateToBedrock(session, session.getInventory().getCursor())); + cursorPacket.setItem(session.getPlayerInventory().getCursor().getItemData(session)); session.sendUpstreamPacket(cursorPacket); } + public static boolean canStack(GeyserItemStack item1, GeyserItemStack item2) { + if (item1.isEmpty() || item2.isEmpty()) + return false; + return item1.getId() == item2.getId() && Objects.equals(item1.getNbt(), item2.getNbt()); + } + public static boolean canStack(ItemStack item1, ItemStack item2) { if (item1 == null || item2 == null) return false; @@ -170,19 +162,16 @@ public class InventoryUtils { */ public static void findOrCreatePickedBlock(GeyserSession session, String itemName) { // Get the inventory to choose a slot to pick - Inventory inventory = session.getInventoryCache().getOpenInventory(); - if (inventory == null) { - inventory = session.getInventory(); - } + PlayerInventory inventory = session.getPlayerInventory(); // Check hotbar for item for (int i = 36; i < 45; i++) { - if (inventory.getItem(i) == null) { + GeyserItemStack geyserItem = inventory.getItem(i); + if (geyserItem.isEmpty()) { continue; } - ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); // If this isn't the item we're looking for - if (!item.getJavaIdentifier().equals(itemName)) { + if (!geyserItem.getItemEntry().getJavaIdentifier().equals(itemName)) { continue; } @@ -193,12 +182,12 @@ public class InventoryUtils { // Check inventory for item for (int i = 9; i < 36; i++) { - if (inventory.getItem(i) == null) { + GeyserItemStack geyserItem = inventory.getItem(i); + if (geyserItem.isEmpty()) { continue; } - ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); // If this isn't the item we're looking for - if (!item.getJavaIdentifier().equals(itemName)) { + if (!geyserItem.getItemEntry().getJavaIdentifier().equals(itemName)) { continue; } @@ -209,10 +198,10 @@ public class InventoryUtils { // If we still have not found the item, and we're in creative, ask for the item from the server. if (session.getGameMode() == GameMode.CREATIVE) { - int slot = session.getInventory().getHeldItemSlot() + 36; - if (session.getInventory().getItemInHand() != null) { // Otherwise we should just use the current slot + int slot = inventory.getHeldItemSlot() + 36; + if (!inventory.getItemInHand().isEmpty()) { // Otherwise we should just use the current slot for (int i = 36; i < 45; i++) { - if (inventory.getItem(i) == null) { + if (inventory.getItem(i).isEmpty()) { slot = i; break; } @@ -221,7 +210,7 @@ public class InventoryUtils { ClientCreativeInventoryActionPacket actionPacket = new ClientCreativeInventoryActionPacket(slot, new ItemStack(ItemRegistry.getItemEntry(itemName).getJavaId())); - if ((slot - 36) != session.getInventory().getHeldItemSlot()) { + if ((slot - 36) != inventory.getHeldItemSlot()) { setHotbarItem(session, slot); } session.sendDownstreamPacket(actionPacket); diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index 5f2179226..93b2caed3 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit 5f21792264a364e32425014e0be79db93593da1e +Subproject commit 93b2caed3c4ecd94b3c77a87f1b2304a7bf4f062 diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 0fae8d3f0..ec8b68297 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 0fae8d3f0de6210a10435a36128db14cb7650ae6 +Subproject commit ec8b68297c4d62a9e4d640a8c7e77977062628ee From 3b3e72d5c3e37e3c0747d63f67ddafef48936733 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 15 Dec 2020 23:01:27 -0500 Subject: [PATCH 002/161] Initial crafting table support --- .../BedrockItemStackRequestTranslator.java | 1 - .../CraftingInventoryTranslator.java | 114 ++++++++++++++++++ .../inventory/InventoryTranslator.java | 4 +- .../holder/BlockInventoryHolder.java | 3 + 4 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java index 00ca83c73..8190bb556 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java @@ -32,7 +32,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.utils.InventoryUtils; @Translator(packet = ItemStackRequestPacket.class) public class BedrockItemStackRequestTranslator extends PacketTranslator { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java new file mode 100644 index 000000000..61dd9b040 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; +import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; +import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +public class CraftingInventoryTranslator extends BaseInventoryTranslator { + private final InventoryHolder holder; + private final InventoryUpdater updater; + + CraftingInventoryTranslator() { + super(10); + int javaBlockState = BlockTranslator.getJavaBlockState("minecraft:crafting_table"); + this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), ContainerType.WORKBENCH); + this.updater = new ContainerInventoryUpdater(); + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + holder.prepareInventory(this, session, inventory); + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + holder.openInventory(this, session, inventory); + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + holder.closeInventory(this, session, inventory); + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + updater.updateInventory(this, session, inventory); + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + updater.updateSlot(this, session, inventory, slot); + } + + @Override + public SlotType getSlotType(int javaSlot) { + if (javaSlot == 0) + return SlotType.OUTPUT; + return SlotType.NORMAL; + } + + + @Override + public int javaSlotToBedrock(int slot) { + if (slot < size) { + return slot == 0 ? 50 : slot + 31; + } + return super.javaSlotToBedrock(slot); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot >= 1 && slot <= 9) { + return new BedrockContainerSlot(ContainerSlotType.CRAFTING_INPUT, slot + 31); + } + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.CRAFTING_OUTPUT, 0); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.CRAFTING_INPUT) { + // Java goes from 1 - 9, left to right then up to down + // Bedrock is the same, but it starts from 32. + return slotInfoData.getSlot() - 31; + } + if (slotInfoData.getContainer() == ContainerSlotType.CRAFTING_OUTPUT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { + return 0; + } + return super.bedrockSlotToJava(slotInfoData); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 69670a3de..4fa54b389 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -58,9 +58,9 @@ public abstract class InventoryTranslator { put(WindowType.GENERIC_9X4, new DoubleChestInventoryTranslator(36)); put(WindowType.GENERIC_9X5, new DoubleChestInventoryTranslator(45)); put(WindowType.GENERIC_9X6, new DoubleChestInventoryTranslator(54)); + put(WindowType.CRAFTING, new CraftingInventoryTranslator()); /*put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); put(WindowType.ANVIL, new AnvilInventoryTranslator()); - put(WindowType.CRAFTING, new CraftingInventoryTranslator()); put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator());*/ put(WindowType.MERCHANT, new MerchantInventoryTranslator()); /*put(WindowType.SMITHING, new SmithingInventoryTranslator()); @@ -295,7 +295,7 @@ public abstract class InventoryTranslator { } craftState = CraftState.RECIPE_ID; recipeId = craftAction.getRecipeNetworkId(); - System.out.println(session.getCraftingRecipes().get(recipeId).toString()); + System.out.println(session.getCraftingRecipes().get(recipeId)); autoCraft = false; break; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index 6afdb25dd..339c8a545 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -38,6 +38,9 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +/** + * Manages the fake block we implement for each inventory. + */ @AllArgsConstructor public class BlockInventoryHolder extends InventoryHolder { private final int blockId; From 988e697a706ba1cbc5617a60b17c209d93395959 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Tue, 15 Dec 2020 23:33:17 -0500 Subject: [PATCH 003/161] Crafting table works --- .../CraftingInventoryTranslator.java | 4 +- .../inventory/InventoryTranslator.java | 31 +++++---- .../updater/CursorInventoryUpdater.java | 64 +++++++++++++++++++ 3 files changed, 85 insertions(+), 14 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java index 61dd9b040..a266074da 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java @@ -32,7 +32,7 @@ import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; -import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; import org.geysermc.connector.network.translators.world.block.BlockTranslator; @@ -44,7 +44,7 @@ public class CraftingInventoryTranslator extends BaseInventoryTranslator { super(10); int javaBlockState = BlockTranslator.getJavaBlockState("minecraft:crafting_table"); this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), ContainerType.WORKBENCH); - this.updater = new ContainerInventoryUpdater(); + this.updater = new CursorInventoryUpdater(); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 4fa54b389..a9ac58cb4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -295,21 +295,28 @@ public abstract class InventoryTranslator { } craftState = CraftState.RECIPE_ID; recipeId = craftAction.getRecipeNetworkId(); - System.out.println(session.getCraftingRecipes().get(recipeId)); + //System.out.println(session.getCraftingRecipes().get(recipeId)); autoCraft = false; break; } - /*case CRAFT_RECIPE_AUTO: { - AutoCraftRecipeStackRequestActionData autoCraftAction = (AutoCraftRecipeStackRequestActionData) action; - if (craftState != CraftState.START) { - return rejectRequest(request); - } - craftState = CraftState.RECIPE_ID; - recipeId = autoCraftAction.getRecipeNetworkId(); - autoCraft = true; - //TODO: reject transaction if crafting grid is not clear - break; - }*/ +// case CRAFT_RECIPE_AUTO: { +// AutoCraftRecipeStackRequestActionData autoCraftAction = (AutoCraftRecipeStackRequestActionData) action; +// if (craftState != CraftState.START) { +// return rejectRequest(request); +// } +// craftState = CraftState.RECIPE_ID; +// recipeId = autoCraftAction.getRecipeNetworkId(); +// Recipe recipe = session.getCraftingRecipes().get(recipeId); +// System.out.println(recipe); +// if (recipe == null) { +// return rejectRequest(request); +// } +//// ClientPrepareCraftingGridPacket packet = new ClientPrepareCraftingGridPacket(session.getOpenInventory().getId(), recipe.getIdentifier(), true); +//// session.sendDownstreamPacket(packet); +// autoCraft = true; +// //TODO: reject transaction if crafting grid is not clear +// break; +// } case CRAFT_RESULTS_DEPRECATED: { CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action; if (craftState != CraftState.RECIPE_ID) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java new file mode 100644 index 000000000..43306ea15 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.updater; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; +import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; + +public class CursorInventoryUpdater extends InventoryUpdater { + + @Override + public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + super.updateInventory(translator, session, inventory); + + for (int i = 0; i < translator.size; i++) { + final int bedrockSlot = translator.javaSlotToBedrock(i); + if (bedrockSlot == 50) + continue; + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(bedrockSlot); + slotPacket.setItem(inventory.getItem(i).getItemData(session)); + session.sendUpstreamPacket(slotPacket); + } + } + + @Override + public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) { + if (super.updateSlot(translator, session, inventory, javaSlot)) + return true; + + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); + slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); + session.sendUpstreamPacket(slotPacket); + return true; + } +} From 929b0ba80cc9d128c51826f1f459232f4853af36 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Wed, 16 Dec 2020 23:52:45 -0500 Subject: [PATCH 004/161] Refactors, and add furnace --- .../inventory/InventoryTranslator.java | 27 ++++--- .../AbstractBlockInventoryTranslator.java | 80 ++++++++++++++++++ .../BaseInventoryTranslator.java | 7 +- .../CraftingInventoryTranslator.java | 56 ++----------- .../MerchantInventoryTranslator.java | 4 +- .../PlayerInventoryTranslator.java | 5 +- .../chest}/ChestInventoryTranslator.java | 4 +- .../DoubleChestInventoryTranslator.java | 2 +- .../SingleChestInventoryTranslator.java | 2 +- .../AbstractFurnaceInventoryTranslator.java | 81 +++++++++++++++++++ .../BlastFurnaceInventoryTranslator.java | 44 ++++++++++ .../furnace/FurnaceInventoryTranslator.java | 44 ++++++++++ .../furnace/SmokerInventoryTranslator.java | 44 ++++++++++ .../world/JavaNotifyClientTranslator.java | 2 +- .../connector/utils/InventoryUtils.java | 4 +- 15 files changed, 336 insertions(+), 70 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java rename connector/src/main/java/org/geysermc/connector/network/translators/inventory/{ => translators}/BaseInventoryTranslator.java (90%) rename connector/src/main/java/org/geysermc/connector/network/translators/inventory/{ => translators}/CraftingInventoryTranslator.java (57%) rename connector/src/main/java/org/geysermc/connector/network/translators/inventory/{ => translators}/MerchantInventoryTranslator.java (96%) rename connector/src/main/java/org/geysermc/connector/network/translators/inventory/{ => translators}/PlayerInventoryTranslator.java (96%) rename connector/src/main/java/org/geysermc/connector/network/translators/inventory/{ => translators/chest}/ChestInventoryTranslator.java (90%) rename connector/src/main/java/org/geysermc/connector/network/translators/inventory/{ => translators/chest}/DoubleChestInventoryTranslator.java (98%) rename connector/src/main/java/org/geysermc/connector/network/translators/inventory/{ => translators/chest}/SingleChestInventoryTranslator.java (96%) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/AbstractFurnaceInventoryTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/BlastFurnaceInventoryTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/FurnaceInventoryTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/SmokerInventoryTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index a9ac58cb4..a0c1a3fa3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -40,6 +40,16 @@ import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.click.Click; import org.geysermc.connector.network.translators.inventory.click.ClickPlan; +import org.geysermc.connector.network.translators.inventory.translators.CraftingInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.translators.MerchantInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.translators.PlayerInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.translators.chest.DoubleChestInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.translators.chest.SingleChestInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.translators.furnace.BlastFurnaceInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.translators.furnace.FurnaceInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.translators.furnace.SmokerInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.utils.InventoryUtils; @@ -63,19 +73,18 @@ public abstract class InventoryTranslator { put(WindowType.ANVIL, new AnvilInventoryTranslator()); put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator());*/ put(WindowType.MERCHANT, new MerchantInventoryTranslator()); - /*put(WindowType.SMITHING, new SmithingInventoryTranslator()); + //put(WindowType.SMITHING, new SmithingInventoryTranslator()); //put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator()); //TODO - InventoryTranslator furnace = new FurnaceInventoryTranslator(); - put(WindowType.FURNACE, furnace); - put(WindowType.BLAST_FURNACE, furnace); - put(WindowType.SMOKER, furnace); + put(WindowType.FURNACE, new FurnaceInventoryTranslator()); + put(WindowType.BLAST_FURNACE, new BlastFurnaceInventoryTranslator()); + put(WindowType.SMOKER, new SmokerInventoryTranslator()); InventoryUpdater containerUpdater = new ContainerInventoryUpdater(); - put(WindowType.GENERIC_3X3, new BlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, containerUpdater)); - put(WindowType.HOPPER, new BlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, containerUpdater)); - put(WindowType.SHULKER_BOX, new BlockInventoryTranslator(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, containerUpdater)); - //put(WindowType.BEACON, new BlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO*/ + //put(WindowType.GENERIC_3X3, new AbstractBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, containerUpdater)); + //put(WindowType.HOPPER, new AbstractBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, containerUpdater)); + //put(WindowType.SHULKER_BOX, new AbstractBlockInventoryTranslator(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, containerUpdater)); + //put(WindowType.BEACON, new AbstractBlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO*/ } }; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java new file mode 100644 index 000000000..bff90c520 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; +import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; +import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; + +/** + * Provided as a base for any inventory that requires a block for opening it + */ +public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTranslator { + private final InventoryHolder holder; + private final InventoryUpdater updater; + + /** + * @param size the amount of slots that the inventory adds alongside the base inventory slots + * @param javaBlockIdentifier a Java block identifier that is used as a temporary block + * @param containerType the container type of this inventory + * @param updater updater + */ + public AbstractBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater) { + super(size); + int javaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier); + this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), containerType); + this.updater = updater; + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + holder.prepareInventory(this, session, inventory); + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + holder.openInventory(this, session, inventory); + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + holder.closeInventory(this, session, inventory); + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + updater.updateInventory(this, session, inventory); + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + updater.updateSlot(this, session, inventory, slot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BaseInventoryTranslator.java similarity index 90% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BaseInventoryTranslator.java index 7d54667bd..48d2ac109 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BaseInventoryTranslator.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.connector.network.translators.inventory.translators; import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; @@ -32,9 +32,12 @@ import org.geysermc.connector.inventory.Container; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.inventory.SlotType; public abstract class BaseInventoryTranslator extends InventoryTranslator { - BaseInventoryTranslator(int size) { + public BaseInventoryTranslator(int size) { super(size); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java similarity index 57% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java index a266074da..2c420ada4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java @@ -23,53 +23,18 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.connector.network.translators.inventory.translators; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; -import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.SlotType; import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; -import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; -public class CraftingInventoryTranslator extends BaseInventoryTranslator { - private final InventoryHolder holder; - private final InventoryUpdater updater; - - CraftingInventoryTranslator() { - super(10); - int javaBlockState = BlockTranslator.getJavaBlockState("minecraft:crafting_table"); - this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), ContainerType.WORKBENCH); - this.updater = new CursorInventoryUpdater(); - } - - @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { - holder.prepareInventory(this, session, inventory); - } - - @Override - public void openInventory(GeyserSession session, Inventory inventory) { - holder.openInventory(this, session, inventory); - } - - @Override - public void closeInventory(GeyserSession session, Inventory inventory) { - holder.closeInventory(this, session, inventory); - } - - @Override - public void updateInventory(GeyserSession session, Inventory inventory) { - updater.updateInventory(this, session, inventory); - } - - @Override - public void updateSlot(GeyserSession session, Inventory inventory, int slot) { - updater.updateSlot(this, session, inventory, slot); +public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslator { + public CraftingInventoryTranslator() { + super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, new CursorInventoryUpdater()); } @Override @@ -79,15 +44,6 @@ public class CraftingInventoryTranslator extends BaseInventoryTranslator { return SlotType.NORMAL; } - - @Override - public int javaSlotToBedrock(int slot) { - if (slot < size) { - return slot == 0 ? 50 : slot + 31; - } - return super.javaSlotToBedrock(slot); - } - @Override public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { if (slot >= 1 && slot <= 9) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/MerchantInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java similarity index 96% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/MerchantInventoryTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java index c4b2fc9a1..e798157d6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/MerchantInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.connector.network.translators.inventory.translators; import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.nukkitx.math.vector.Vector3f; @@ -38,6 +38,8 @@ import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.inventory.MerchantContainer; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.SlotType; import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java similarity index 96% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java index 5ace8fde0..286c3f63b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.connector.network.translators.inventory.translators; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.window.WindowType; @@ -34,6 +34,9 @@ import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.inventory.SlotType; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.utils.InventoryUtils; import org.geysermc.connector.utils.LanguageUtils; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/ChestInventoryTranslator.java similarity index 90% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/ChestInventoryTranslator.java index df97563e5..1e81bb67d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/ChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/ChestInventoryTranslator.java @@ -23,11 +23,13 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.connector.network.translators.inventory.translators.chest; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.translators.BaseInventoryTranslator; import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater; import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java similarity index 98% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java index 1183b21da..f807cfe08 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.connector.network.translators.inventory.translators.chest; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3i; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java similarity index 96% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java index 45860dcd3..43e060064 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java @@ -23,7 +23,7 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.connector.network.translators.inventory.translators.chest; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import org.geysermc.connector.inventory.Inventory; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/AbstractFurnaceInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/AbstractFurnaceInventoryTranslator.java new file mode 100644 index 000000000..b5f5b97f1 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/AbstractFurnaceInventoryTranslator.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators.furnace; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.SlotType; +import org.geysermc.connector.network.translators.inventory.translators.AbstractBlockInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; + +public abstract class AbstractFurnaceInventoryTranslator extends AbstractBlockInventoryTranslator { + AbstractFurnaceInventoryTranslator(String javaBlockIdentifier, ContainerType containerType) { + super(3, javaBlockIdentifier, containerType, new ContainerInventoryUpdater()); + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + ContainerSetDataPacket dataPacket = new ContainerSetDataPacket(); + dataPacket.setWindowId((byte) inventory.getId()); + switch (key) { + case 0: + dataPacket.setProperty(ContainerSetDataPacket.FURNACE_LIT_TIME); + break; + case 1: + dataPacket.setProperty(ContainerSetDataPacket.FURNACE_LIT_DURATION); + break; + case 2: + dataPacket.setProperty(ContainerSetDataPacket.FURNACE_TICK_COUNT); + break; + default: + return; + } + dataPacket.setValue(value); + session.sendUpstreamPacket(dataPacket); + } + + @Override + public SlotType getSlotType(int javaSlot) { + if (javaSlot == 2) + return SlotType.FURNACE_OUTPUT; + return SlotType.NORMAL; + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 1) { + return new BedrockContainerSlot(ContainerSlotType.FURNACE_FUEL, javaSlotToBedrock(slot)); + } + if (slot == 2) { + return new BedrockContainerSlot(ContainerSlotType.FURNACE_OUTPUT, javaSlotToBedrock(slot)); + } + return super.javaSlotToBedrockContainer(slot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/BlastFurnaceInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/BlastFurnaceInventoryTranslator.java new file mode 100644 index 000000000..b7834dde9 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/BlastFurnaceInventoryTranslator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators.furnace; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; + +public class BlastFurnaceInventoryTranslator extends AbstractFurnaceInventoryTranslator { + public BlastFurnaceInventoryTranslator() { + super("minecraft:blast_furnace[facing=north,lit=false]", ContainerType.BLAST_FURNACE); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.BLAST_FURNACE_INGREDIENT, javaSlotToBedrock(slot)); + } + return super.javaSlotToBedrockContainer(slot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/FurnaceInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/FurnaceInventoryTranslator.java new file mode 100644 index 000000000..f3d759412 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/FurnaceInventoryTranslator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators.furnace; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; + +public class FurnaceInventoryTranslator extends AbstractFurnaceInventoryTranslator { + public FurnaceInventoryTranslator() { + super("minecraft:furnace[facing=north,lit=false]", ContainerType.FURNACE); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.FURNACE_INGREDIENT, javaSlotToBedrock(slot)); + } + return super.javaSlotToBedrockContainer(slot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/SmokerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/SmokerInventoryTranslator.java new file mode 100644 index 000000000..75eb33d97 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/SmokerInventoryTranslator.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators.furnace; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; + +public class SmokerInventoryTranslator extends AbstractFurnaceInventoryTranslator { + public SmokerInventoryTranslator() { + super("minecraft:smoker[facing=north,lit=false]", ContainerType.SMOKER); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.SMOKER_INGREDIENT, javaSlotToBedrock(slot)); + } + return super.javaSlotToBedrockContainer(slot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java index 44c1ad9b5..11f20f975 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaNotifyClientTranslator.java @@ -42,7 +42,7 @@ import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.inventory.PlayerInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.translators.PlayerInventoryTranslator; import org.geysermc.connector.utils.LocaleUtils; @Translator(packet = ServerNotifyClientPacket.class) diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java index 876717a83..cbdfa9c56 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -43,11 +43,9 @@ import org.geysermc.connector.common.ChatColor; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.DoubleChestInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.translators.chest.DoubleChestInventoryTranslator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; import java.util.Collections; import java.util.Objects; From 33a86485dca06cff48e3e162c66927851e9db3ae Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Thu, 17 Dec 2020 11:46:11 -0500 Subject: [PATCH 005/161] Implement dropper/dispenser, hopper, shulker --- .../BedrockItemStackRequestTranslator.java | 1 + .../inventory/InventoryTranslator.java | 14 ++---- .../CraftingInventoryTranslator.java | 2 +- .../GenericBlockInventoryTranslator.java | 48 +++++++++++++++++++ .../ShulkerInventoryTranslator.java | 45 +++++++++++++++++ .../AbstractFurnaceInventoryTranslator.java | 2 +- .../updater/ContainerInventoryUpdater.java | 3 +- .../updater/CursorInventoryUpdater.java | 1 + 8 files changed, 104 insertions(+), 12 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/ShulkerInventoryTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java index 8190bb556..1ab68106e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java @@ -38,6 +38,7 @@ public class BedrockItemStackRequestTranslator extends PacketTranslator Date: Thu, 17 Dec 2020 21:47:18 -0500 Subject: [PATCH 006/161] Brewing stand support; other attempts --- .../inventory/InventoryTranslator.java | 42 ++++++- .../BrewingInventoryTranslator.java | 113 ++++++++++++++++++ 2 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index fe22d397c..35bbb2cdd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.connector.network.translators.inventory; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; @@ -67,9 +68,9 @@ public abstract class InventoryTranslator { put(WindowType.GENERIC_9X6, new DoubleChestInventoryTranslator(54)); put(WindowType.CRAFTING, new CraftingInventoryTranslator()); put(WindowType.SHULKER_BOX, new ShulkerInventoryTranslator()); - /*put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); - put(WindowType.ANVIL, new AnvilInventoryTranslator()); - put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator());*/ + put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); + //put(WindowType.ANVIL, new AnvilInventoryTranslator()); + //put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); put(WindowType.MERCHANT, new MerchantInventoryTranslator()); //put(WindowType.SMITHING, new SmithingInventoryTranslator()); //put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator()); //TODO @@ -81,6 +82,11 @@ public abstract class InventoryTranslator { put(WindowType.GENERIC_3X3, new GenericBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER)); put(WindowType.HOPPER, new GenericBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER)); //put(WindowType.BEACON, new AbstractBlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO*/ + + //put(WindowType.CARTOGRAPHY + //put(WindowType.STONECUTTER + //put(WindowType.LOOM + //put(WindowType. } }; @@ -129,8 +135,12 @@ public abstract class InventoryTranslator { case TAKE: case PLACE: { TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; - if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) + if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) { + session.getConnector().getLogger().error("DEBUG: About to reject request."); + session.getConnector().getLogger().error("Source: " + transferAction.getSource().toString() + " Result: " + checkNetId(session, inventory, transferAction.getSource())); + session.getConnector().getLogger().error("Destination: " + transferAction.getDestination().toString() + " Result: " + checkNetId(session, inventory, transferAction.getDestination())); return rejectRequest(request); + } if (isCursor(transferAction.getSource()) && isCursor(transferAction.getDestination())) { //??? return rejectRequest(request); @@ -273,6 +283,30 @@ public abstract class InventoryTranslator { CraftCreativeStackRequestActionData creativeAction = (CraftCreativeStackRequestActionData) action; System.out.println(creativeAction.getCreativeItemNetworkId()); } + case DESTROY: { + //TODO: Yeah this doesn't work yet. + + // Only called when a creative client wants to destroy an item... I think - Camotoy + DestroyStackRequestActionData destroyAction = (DestroyStackRequestActionData) action; + if (session.getGameMode() == GameMode.CREATIVE) { + if (isCursor(destroyAction.getSource())) { + session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY); + return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); + } else { + int javaSlot = bedrockSlotToJava(destroyAction.getSource()); + inventory.setItem(javaSlot, GeyserItemStack.EMPTY); + ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( + javaSlot, + new ItemStack(0) + ); + session.sendDownstreamPacket(creativeActionPacket); + Set affectedSlots = Collections.singleton(javaSlot); + return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); + } + } else { + return rejectRequest(request); + } + } default: return rejectRequest(request); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java new file mode 100644 index 000000000..6bc30a90f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.packet.ContainerSetDataPacket; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; + +public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator { + public BrewingInventoryTranslator() { + super(5, "minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=false]", ContainerType.BREWING_STAND, ContainerInventoryUpdater.INSTANCE); + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + super.openInventory(session, inventory); + ContainerSetDataPacket dataPacket = new ContainerSetDataPacket(); + dataPacket.setWindowId((byte) inventory.getId()); + dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_FUEL_TOTAL); + dataPacket.setValue(20); + session.sendUpstreamPacket(dataPacket); + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + ContainerSetDataPacket dataPacket = new ContainerSetDataPacket(); + dataPacket.setWindowId((byte) inventory.getId()); + switch (key) { + case 0: + dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_BREW_TIME); + break; + case 1: + dataPacket.setProperty(ContainerSetDataPacket.BREWING_STAND_FUEL_AMOUNT); + break; + default: + return; + } + dataPacket.setValue(value); + session.sendUpstreamPacket(dataPacket); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + System.out.println("Brewing stand: " + slotInfoData); + if (slotInfoData.getContainer() == ContainerSlotType.BREWING_INPUT) { + // Ingredient + // TODO: This hasn't worked and then suddenly, it did. + return 3; + } + if (slotInfoData.getContainer() == ContainerSlotType.BREWING_RESULT) { + // Potions + return slotInfoData.getSlot() - 1; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public int javaSlotToBedrock(int slot) { + switch (slot) { + case 0: + return 1; + case 1: + return 2; + case 2: + return 3; + case 3: + return 0; + } + return super.javaSlotToBedrock(slot); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0 || slot == 1 || slot == 2) { + return new BedrockContainerSlot(ContainerSlotType.BREWING_RESULT, javaSlotToBedrock(slot)); + } + if (slot == 3) { + return new BedrockContainerSlot(ContainerSlotType.BREWING_INPUT, 0); + } + if (slot == 4) { + return new BedrockContainerSlot(ContainerSlotType.BREWING_FUEL, 4); + } + return super.javaSlotToBedrockContainer(slot); + } +} From f47cf32d903c2822da814f17c33e0e6c5f2de851 Mon Sep 17 00:00:00 2001 From: DoctorMacc Date: Sat, 19 Dec 2020 19:19:44 -0500 Subject: [PATCH 007/161] Make my life tolerable --- .../translators/inventory/InventoryTranslator.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 35bbb2cdd..52fb6e96c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -136,6 +136,10 @@ public abstract class InventoryTranslator { case PLACE: { TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) { + if (session.getGameMode().equals(GameMode.CREATIVE) && transferAction.getSource().getContainer() == ContainerSlotType.CRAFTING_INPUT && + transferAction.getSource().getSlot() >= 28 && transferAction.getSource().getSlot() <= 31) { + return rejectRequest(request, false); + } session.getConnector().getLogger().error("DEBUG: About to reject request."); session.getConnector().getLogger().error("Source: " + transferAction.getSource().toString() + " Result: " + checkNetId(session, inventory, transferAction.getSource())); session.getConnector().getLogger().error("Destination: " + transferAction.getDestination().toString() + " Result: " + checkNetId(session, inventory, transferAction.getDestination())); @@ -504,7 +508,14 @@ public abstract class InventoryTranslator { } public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequestPacket.Request request) { - new Throwable("DEBUGGING: ItemStackRequest rejected").printStackTrace(); //TODO: temporary debugging + return rejectRequest(request, true); + } + + public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequestPacket.Request request, boolean throwError) { + if (throwError) { + // Currently for debugging, but might be worth it to keep in the future if something goes terribly wrong. + new Throwable("DEBUGGING: ItemStackRequest rejected").printStackTrace(); + } return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.ERROR, request.getRequestId(), Collections.emptyList()); } From c6b4d163a1634d1c94b8c027555208d546160502 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 21 Dec 2020 21:09:14 -0500 Subject: [PATCH 008/161] Improve creative support --- .../inventory/InventoryTranslator.java | 130 ++++++++++++++---- .../inventory/click/ClickPlan.java | 12 +- .../java/window/JavaSetSlotTranslator.java | 2 +- 3 files changed, 117 insertions(+), 27 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 52fb6e96c..f532beea2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -31,10 +31,13 @@ import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; import lombok.AllArgsConstructor; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; @@ -92,6 +95,7 @@ public abstract class InventoryTranslator { public static final int PLAYER_INVENTORY_SIZE = 36; public static final int PLAYER_INVENTORY_OFFSET = 9; + private static final int MAX_ITEM_STACK_SIZE = 64; public final int size; public abstract void prepareInventory(GeyserSession session, Inventory inventory); @@ -114,6 +118,7 @@ public abstract class InventoryTranslator { if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE || firstAction.getType() == StackRequestActionType.CRAFT_RECIPE_AUTO) { responsePacket.getEntries().add(translateCraftingRequest(session, inventory, request)); } else if (firstAction.getType() == StackRequestActionType.CRAFT_CREATIVE) { + // This is also used for pulling items out of creative responsePacket.getEntries().add(translateCreativeRequest(session, inventory, request)); } else { responsePacket.getEntries().add(translateRequest(session, inventory, request)); @@ -129,6 +134,7 @@ public abstract class InventoryTranslator { public ItemStackResponsePacket.Response translateRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { System.out.println(request); ClickPlan plan = new ClickPlan(session, this, inventory); + IntSet affectedSlots = new IntOpenHashSet(); for (StackRequestActionData action : request.getActions()) { GeyserItemStack cursor = session.getPlayerInventory().getCursor(); switch (action.getType()) { @@ -148,6 +154,73 @@ public abstract class InventoryTranslator { if (isCursor(transferAction.getSource()) && isCursor(transferAction.getDestination())) { //??? return rejectRequest(request); + } else if (session.getGameMode().equals(GameMode.CREATIVE) && inventory instanceof PlayerInventory) { // TODO: does the Java server use the player inventory in all instances? + // Creative acts a little differently because it just edits slots + int sourceSlot = bedrockSlotToJava(transferAction.getSource()); + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + boolean sourceIsCursor = isCursor(transferAction.getSource()); + boolean destIsCursor = isCursor(transferAction.getDestination()); + + GeyserItemStack sourceItem = sourceIsCursor ? session.getPlayerInventory().getCursor() : + inventory.getItem(sourceSlot); + GeyserItemStack newItem = sourceItem.copy(); + if (sourceIsCursor) { + GeyserItemStack destItem = inventory.getItem(destSlot); + if (destItem.getId() == sourceItem.getId()) { + // Combining items + int itemsLeftOver = destItem.getAmount() + transferAction.getCount(); + if (itemsLeftOver > MAX_ITEM_STACK_SIZE) { + // Items will remain in cursor because destination slot gets set to 64 + destItem.setAmount(MAX_ITEM_STACK_SIZE); + sourceItem.setAmount(itemsLeftOver - MAX_ITEM_STACK_SIZE); + } else { + // Cursor will be emptied + destItem.setAmount(itemsLeftOver); + session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY); + } + ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( + destSlot, + destItem.getItemStack() + ); + session.sendDownstreamPacket(creativeActionPacket); + affectedSlots.add(destSlot); + break; + } + } + // Update the item count with however much the client took + newItem.setAmount(transferAction.getCount()); + // Remove that amount from the existing item + sourceItem.setAmount(sourceItem.getAmount() - transferAction.getCount()); + if (sourceItem.isEmpty()) { + // Item is basically deleted + if (sourceIsCursor) { + session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY); + } else { + inventory.setItem(sourceSlot, GeyserItemStack.EMPTY); + } + } + if (destIsCursor) { + session.getPlayerInventory().setCursor(newItem); + } else { + inventory.setItem(destSlot, newItem); + } + GeyserItemStack itemToUpdate = destIsCursor ? sourceItem : newItem; + // The Java server doesn't care about what's in the mouse in creative mode, so we just need to track + // which inventory slot the client modified + ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( + destIsCursor ? sourceSlot : destSlot, + itemToUpdate.isEmpty() ? new ItemStack(0) : itemToUpdate.getItemStack() + ); + session.sendDownstreamPacket(creativeActionPacket); + System.out.println(creativeActionPacket); + + if (!sourceIsCursor) { // Cursor is always added for us as an affected slot + affectedSlots.add(sourceSlot); + } + if (!destIsCursor) { + affectedSlots.add(destSlot); + } + } else if (isCursor(transferAction.getSource())) { //releasing cursor int sourceAmount = cursor.getAmount(); int destSlot = bedrockSlotToJava(transferAction.getDestination()); @@ -286,37 +359,38 @@ public abstract class InventoryTranslator { case CRAFT_CREATIVE: { CraftCreativeStackRequestActionData creativeAction = (CraftCreativeStackRequestActionData) action; System.out.println(creativeAction.getCreativeItemNetworkId()); + break; } case DESTROY: { - //TODO: Yeah this doesn't work yet. - // Only called when a creative client wants to destroy an item... I think - Camotoy DestroyStackRequestActionData destroyAction = (DestroyStackRequestActionData) action; - if (session.getGameMode() == GameMode.CREATIVE) { - if (isCursor(destroyAction.getSource())) { - session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY); - return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); - } else { - int javaSlot = bedrockSlotToJava(destroyAction.getSource()); - inventory.setItem(javaSlot, GeyserItemStack.EMPTY); - ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( - javaSlot, - new ItemStack(0) - ); - session.sendDownstreamPacket(creativeActionPacket); - Set affectedSlots = Collections.singleton(javaSlot); - return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); - } - } else { + if (!session.getGameMode().equals(GameMode.CREATIVE)) { + // If this happens, let's throw an error and figure out why. return rejectRequest(request); } + if (!isCursor(destroyAction.getSource())) { + int javaSlot = bedrockSlotToJava(destroyAction.getSource()); + ClientCreativeInventoryActionPacket destroyItemPacket = new ClientCreativeInventoryActionPacket( + javaSlot, + new ItemStack(0) + ); + session.sendDownstreamPacket(destroyItemPacket); + System.out.println(destroyItemPacket); + inventory.setItem(javaSlot, GeyserItemStack.EMPTY); + affectedSlots.add(javaSlot); + } else { + // Just sync up the item on our end, since the server doesn't care what's in our cursor + session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY); + } + break; } default: return rejectRequest(request); } } plan.execute(false); - return acceptRequest(request, makeContainerEntries(session, inventory, plan.getAffectedSlots())); + affectedSlots.addAll(plan.getAffectedSlots()); + return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); } public ItemStackResponsePacket.Response translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { @@ -480,18 +554,26 @@ public abstract class InventoryTranslator { if (transferAction.getSource().getContainer() != ContainerSlotType.CREATIVE_OUTPUT) { return rejectRequest(request); } + // Reference the creative items list we send to the client to know what it's asking of us + ItemData creativeItem = ItemRegistry.CREATIVE_ITEMS[creativeId - 1]; + // Get the correct count + creativeItem = ItemData.of(creativeItem.getId(), creativeItem.getDamage(), transferAction.getCount(), creativeItem.getTag()); + ItemStack javaCreativeItem = ItemTranslator.translateToJava(creativeItem); + if (isCursor(transferAction.getDestination())) { - session.getPlayerInventory().setCursor(GeyserItemStack.from(ItemTranslator.translateToJava(ItemRegistry.CREATIVE_ITEMS[creativeId]), session.getItemNetId().getAndIncrement())); //TODO - return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); + session.getPlayerInventory().setCursor(GeyserItemStack.from(javaCreativeItem, session.getItemNetId().getAndIncrement())); + return acceptRequest(request, Collections.singletonList( + new ItemStackResponsePacket.ContainerEntry(ContainerSlotType.CURSOR, + Collections.singletonList(makeItemEntry(session, 0, session.getPlayerInventory().getCursor()))))); } else { int javaSlot = bedrockSlotToJava(transferAction.getDestination()); - ItemStack javaItem = ItemTranslator.translateToJava(ItemRegistry.CREATIVE_ITEMS[creativeId - 1]); //TODO - inventory.setItem(javaSlot, GeyserItemStack.from(javaItem, session.getItemNetId().getAndIncrement())); + inventory.setItem(javaSlot, GeyserItemStack.from(javaCreativeItem, session.getItemNetId().getAndIncrement())); ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( javaSlot, - javaItem + javaCreativeItem ); session.sendDownstreamPacket(creativeActionPacket); + System.out.println(creativeActionPacket); Set affectedSlots = Collections.singleton(javaSlot); return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java index f4cf35617..4e5c28552 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java @@ -31,6 +31,8 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientConfi import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientWindowActionPacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; import lombok.Value; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; @@ -193,8 +195,11 @@ public class ClickPlan { } } - public Set getAffectedSlots() { - Set affectedSlots = new HashSet<>(); + /** + * @return a new set of all affected slots. This isn't a constant variable; it's newly generated each time it is run. + */ + public IntSet getAffectedSlots() { + IntSet affectedSlots = new IntOpenHashSet(); for (ClickAction action : plan) { if (translator.getSlotType(action.slot) == SlotType.NORMAL && action.slot != Click.OUTSIDE_SLOT) { affectedSlots.add(action.slot); @@ -206,6 +211,9 @@ public class ClickPlan { @Value private static class ClickAction { Click click; + /** + * Java slot + */ int slot; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java index 22a0edcac..531242fca 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.java.window; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerSetSlotPacket; -import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; @@ -40,6 +39,7 @@ public class JavaSetSlotTranslator extends PacketTranslator @Override public void translate(ServerSetSlotPacket packet, GeyserSession session) { + System.out.println(packet.toString()); session.addInventoryTask(() -> { if (packet.getWindowId() == 255) { //cursor GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); From 009905184eb468212403a73375ec78157472ad7d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 21 Dec 2020 22:44:01 -0500 Subject: [PATCH 009/161] Add grindstone and smithing table --- .../inventory/InventoryTranslator.java | 17 +++- .../EnchantingInventoryTranslator.java | 35 +++++++++ .../GrindstoneInventoryTranslator.java | 78 +++++++++++++++++++ .../SmithingInventoryTranslator.java | 78 +++++++++++++++++++ 4 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index f532beea2..cf63811a2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -73,9 +73,9 @@ public abstract class InventoryTranslator { put(WindowType.SHULKER_BOX, new ShulkerInventoryTranslator()); put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); //put(WindowType.ANVIL, new AnvilInventoryTranslator()); - //put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); + put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); put(WindowType.MERCHANT, new MerchantInventoryTranslator()); - //put(WindowType.SMITHING, new SmithingInventoryTranslator()); + put(WindowType.SMITHING, new SmithingInventoryTranslator()); //put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator()); //TODO put(WindowType.FURNACE, new FurnaceInventoryTranslator()); @@ -154,7 +154,7 @@ public abstract class InventoryTranslator { if (isCursor(transferAction.getSource()) && isCursor(transferAction.getDestination())) { //??? return rejectRequest(request); - } else if (session.getGameMode().equals(GameMode.CREATIVE) && inventory instanceof PlayerInventory) { // TODO: does the Java server use the player inventory in all instances? + } else if (session.getGameMode().equals(GameMode.CREATIVE) && inventory instanceof PlayerInventory) { // TODO: does the Java server use this stuff all the time in creative? // Creative acts a little differently because it just edits slots int sourceSlot = bedrockSlotToJava(transferAction.getSource()); int destSlot = bedrockSlotToJava(transferAction.getDestination()); @@ -369,6 +369,7 @@ public abstract class InventoryTranslator { return rejectRequest(request); } if (!isCursor(destroyAction.getSource())) { + // Item exists; let's remove it from the inventory int javaSlot = bedrockSlotToJava(destroyAction.getSource()); ClientCreativeInventoryActionPacket destroyItemPacket = new ClientCreativeInventoryActionPacket( javaSlot, @@ -384,6 +385,16 @@ public abstract class InventoryTranslator { } break; } + // These three are called for the grindstone + case CONSUME: { + break; + } + case CRAFT_NON_IMPLEMENTED_DEPRECATED: { + break; + } + case CRAFT_RESULTS_DEPRECATED: { + break; + } default: return rejectRequest(request); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java new file mode 100644 index 000000000..a8b13be4c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; + +public class EnchantingInventoryTranslator extends AbstractBlockInventoryTranslator { + public EnchantingInventoryTranslator() { + super(2, "minecraft:enchanting_table", ContainerType.ENCHANTMENT, CursorInventoryUpdater.INSTANCE); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java new file mode 100644 index 000000000..684b25e80 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; + +public class GrindstoneInventoryTranslator extends AbstractBlockInventoryTranslator { + public GrindstoneInventoryTranslator() { + super(3, "minecraft:grindstone[face=floor,facing=north]", ContainerType.GRINDSTONE, CursorInventoryUpdater.INSTANCE); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.GRINDSTONE_INPUT) { + return 0; + } + if (slotInfoData.getContainer() == ContainerSlotType.GRINDSTONE_ADDITIONAL) { + return 1; + } + if (slotInfoData.getContainer() == ContainerSlotType.GRINDSTONE_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { + return 2; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + switch (slot) { + case 0: + return new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_INPUT, 16); + case 1: + return new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_ADDITIONAL, 17); + case 2: + return new BedrockContainerSlot(ContainerSlotType.GRINDSTONE_RESULT, 50); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int javaSlotToBedrock(int slot) { + switch (slot) { + case 0: + return 16; + case 1: + return 17; + case 2: + return 50; + } + return super.javaSlotToBedrock(slot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java new file mode 100644 index 000000000..fba8a8d6e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; + +public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslator { + public SmithingInventoryTranslator() { + super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, CursorInventoryUpdater.INSTANCE); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.SMITHING_TABLE_INPUT) { + return 0; + } + if (slotInfoData.getContainer() == ContainerSlotType.SMITHING_TABLE_MATERIAL) { + return 1; + } + if (slotInfoData.getContainer() == ContainerSlotType.SMITHING_TABLE_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { + return 2; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + switch (slot) { + case 0: + return new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_INPUT, 51); + case 1: + return new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_MATERIAL, 52); + case 2: + return new BedrockContainerSlot(ContainerSlotType.SMITHING_TABLE_RESULT, 50); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int javaSlotToBedrock(int slot) { + switch (slot) { + case 0: + return 51; + case 1: + return 52; + case 2: + return 50; + } + return super.javaSlotToBedrock(slot); + } +} From f4f804e1ca8045f7a25c92fc470d18bb996c4de5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 23 Dec 2020 01:21:00 -0500 Subject: [PATCH 010/161] Enchantment table works; anvil is almost there --- .../connector/inventory/AnvilContainer.java | 34 ++++ .../inventory/EnchantingContainer.java | 53 +++++ .../inventory/EnchantmentInventory.java | 40 ---- .../connector/inventory/FurnaceInventory.java | 1 + .../inventory/GeyserEnchantOption.java | 78 ++++++++ .../bedrock/BedrockFilterTextTranslator.java | 47 +++++ .../inventory/InventoryTranslator.java | 88 ++++++-- .../translators/AnvilInventoryTranslator.java | 87 ++++++++ .../CraftingInventoryTranslator.java | 4 +- .../EnchantingInventoryTranslator.java | 188 +++++++++++++++++- .../GrindstoneInventoryTranslator.java | 4 +- .../MerchantInventoryTranslator.java | 3 +- .../SmithingInventoryTranslator.java | 4 +- .../updater/CursorInventoryUpdater.java | 65 ------ .../inventory/updater/UIInventoryUpdater.java | 2 +- .../window/JavaWindowPropertyTranslator.java | 1 + 16 files changed, 566 insertions(+), 133 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java delete mode 100644 connector/src/main/java/org/geysermc/connector/inventory/EnchantmentInventory.java create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java diff --git a/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java new file mode 100644 index 000000000..d940ac75b --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.inventory; + +import com.github.steveice10.mc.protocol.data.game.window.WindowType; + +public class AnvilContainer extends Container { + public AnvilContainer(String title, int id, WindowType windowType, int size, PlayerInventory playerInventory) { + super(title, id, windowType, size, playerInventory); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java new file mode 100644 index 000000000..c8b2bef13 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.inventory; + +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData; +import lombok.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, WindowType windowType, int size, PlayerInventory playerInventory) { + super(title, id, windowType, size, playerInventory); + + enchantOptions = new EnchantOptionData[3]; + geyserEnchantOptions = new GeyserEnchantOption[3]; + for (int i = 0; i < geyserEnchantOptions.length; i++) { + geyserEnchantOptions[i] = new GeyserEnchantOption(i); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/inventory/EnchantmentInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/EnchantmentInventory.java deleted file mode 100644 index 65debc486..000000000 --- a/connector/src/main/java/org/geysermc/connector/inventory/EnchantmentInventory.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - * - */ - -package org.geysermc.connector.inventory; - -import com.github.steveice10.mc.protocol.data.game.window.WindowType; -import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData; -import lombok.Getter; - -@Getter -public class EnchantmentInventory extends Inventory { - private EnchantOptionData[] enchantOptions; - - public EnchantmentInventory(String title, int id, WindowType windowType, int size) { - super(title, id, windowType, size); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java index 4dc098d89..0af76244d 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java @@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.window.WindowType; import lombok.Getter; import lombok.Setter; +//TODO: Figure out what this is and if we should remove it @Getter public class FurnaceInventory extends Inventory { @Setter diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java new file mode 100644 index 000000000..ea58372e2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.inventory; + +import com.nukkitx.protocol.bedrock.data.inventory.EnchantData; +import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData; +import lombok.Getter; +import lombok.Setter; +import org.geysermc.connector.network.session.GeyserSession; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * A mutable "wrapper" around {@link EnchantOptionData} + */ +@Setter +public class GeyserEnchantOption { + private static final List EMPTY = Collections.emptyList(); + /** + * This: https://cdn.discordapp.com/attachments/613168850925649981/791030657169227816/unknown.png + * is controlled by the server. + * So, of course, we have to throw in some easter eggs. ;) + */ + private static final List ENCHANT_NAMES = Arrays.asList("tougher armor", "lukeeey", "fall better", + "explode less", "camo toy", "breathe better", "rtm five one six", "armor stab", "water walk", "you are elsa", + "tim two zero three", "fast walk nether", "oof ouch owie", "enemy on fire", "spider sad", "aj ferguson", "redned", + "more items thx", "long sword reach", "fast tool", "give me block", "less breaky break", "cube craft", + "strong arrow", "fist arrow", "spicy arrow", "many many arrows", "geyser", "come here fish", "i like this", + "stabby stab", "supreme mortal", "avatar i guess", "more arrows", "fly finder seventeen", "in and out", + "xp heals tools", "dragon proxy waz here"); + + @Getter + private final int javaIndex; + + private int xpCost = 0; + private int javaEnchantIndex = -1; + private int bedrockEnchantIndex = -1; + private int enchantLevel = -1; + + public GeyserEnchantOption(int javaIndex) { + this.javaIndex = javaIndex; + } + + public EnchantOptionData build(GeyserSession session) { + if (enchantLevel == -1) { + // Should not be sent to the client, as it is supposed to be empty + return null; + } + return new EnchantOptionData(xpCost, javaIndex + 16, EMPTY, + Collections.singletonList(new EnchantData(bedrockEnchantIndex, enchantLevel)), EMPTY, + javaEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(javaEnchantIndex), session.getItemNetId().incrementAndGet()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java new file mode 100644 index 000000000..cf06acf58 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.bedrock; + +import com.nukkitx.protocol.bedrock.packet.FilterTextPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +/** + * Used to send strings to the client and filter out unwanted words. + * Java doesn't care, so we don't care, and we approve all strings. + */ +@Translator(packet = FilterTextPacket.class) +public class BedrockFilterTextTranslator extends PacketTranslator { + + @Override + public void translate(FilterTextPacket packet, GeyserSession session) { + // TODO: Bedrock doesn't send this. Why? + System.out.println(packet.toString()); + packet.setFromServer(true); + session.sendUpstreamPacket(packet); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index cf63811a2..719a20922 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -62,29 +62,36 @@ public abstract class InventoryTranslator { public static final Map INVENTORY_TRANSLATORS = new HashMap() { { + /* Player Inventory */ put(null, new PlayerInventoryTranslator()); //player inventory + + /* Chest UIs */ put(WindowType.GENERIC_9X1, new SingleChestInventoryTranslator(9)); put(WindowType.GENERIC_9X2, new SingleChestInventoryTranslator(18)); put(WindowType.GENERIC_9X3, new SingleChestInventoryTranslator(27)); put(WindowType.GENERIC_9X4, new DoubleChestInventoryTranslator(36)); put(WindowType.GENERIC_9X5, new DoubleChestInventoryTranslator(45)); put(WindowType.GENERIC_9X6, new DoubleChestInventoryTranslator(54)); - put(WindowType.CRAFTING, new CraftingInventoryTranslator()); - put(WindowType.SHULKER_BOX, new ShulkerInventoryTranslator()); - put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); - //put(WindowType.ANVIL, new AnvilInventoryTranslator()); - put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); - put(WindowType.MERCHANT, new MerchantInventoryTranslator()); - put(WindowType.SMITHING, new SmithingInventoryTranslator()); - //put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator()); //TODO + /* Furnaces */ put(WindowType.FURNACE, new FurnaceInventoryTranslator()); put(WindowType.BLAST_FURNACE, new BlastFurnaceInventoryTranslator()); put(WindowType.SMOKER, new SmokerInventoryTranslator()); + /* Specific Inventories */ + put(WindowType.ANVIL, new AnvilInventoryTranslator()); + put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); + put(WindowType.CRAFTING, new CraftingInventoryTranslator()); + put(WindowType.ENCHANTMENT, new EnchantingInventoryTranslator()); + put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); + put(WindowType.MERCHANT, new MerchantInventoryTranslator()); + put(WindowType.SHULKER_BOX, new ShulkerInventoryTranslator()); + put(WindowType.SMITHING, new SmithingInventoryTranslator()); + + /* Generics */ put(WindowType.GENERIC_3X3, new GenericBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER)); put(WindowType.HOPPER, new GenericBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER)); - //put(WindowType.BEACON, new AbstractBlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO*/ + //put(WindowType.BEACON, new AbstractBlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO //put(WindowType.CARTOGRAPHY //put(WindowType.STONECUTTER @@ -110,12 +117,30 @@ public abstract class InventoryTranslator { public abstract SlotType getSlotType(int javaSlot); public abstract Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory); + /** + * Should be overrided if this request matches a certain criteria and shouldn't be treated normally. + * E.G. anvil renaming or enchanting + */ + public boolean shouldHandleRequestFirst(StackRequestActionData action) { + return false; + } + + /** + * If {@link #shouldHandleRequestFirst(StackRequestActionData)} returns true, this will be called + */ + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + return null; + } + public void translateRequests(GeyserSession session, Inventory inventory, List requests) { ItemStackResponsePacket responsePacket = new ItemStackResponsePacket(); for (ItemStackRequestPacket.Request request : requests) { if (request.getActions().length > 0) { StackRequestActionData firstAction = request.getActions()[0]; - if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE || firstAction.getType() == StackRequestActionType.CRAFT_RECIPE_AUTO) { + if (shouldHandleRequestFirst(firstAction)) { + // Some special request that shouldn't be processed normally + responsePacket.getEntries().add(translateSpecialRequest(session, inventory, request)); + } else if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE || firstAction.getType() == StackRequestActionType.CRAFT_RECIPE_AUTO) { responsePacket.getEntries().add(translateCraftingRequest(session, inventory, request)); } else if (firstAction.getType() == StackRequestActionType.CRAFT_CREATIVE) { // This is also used for pulling items out of creative @@ -335,12 +360,30 @@ public abstract class InventoryTranslator { return rejectRequest(request); if (isCursor(dropAction.getSource())) { //clicking outside of window - int sourceAmount = plan.getCursor().getAmount(); - if (dropAction.getCount() == sourceAmount) { //drop all - plan.add(Click.LEFT_OUTSIDE, Click.OUTSIDE_SLOT); - } else { //drop some - for (int i = 0; i < dropAction.getCount(); i++) { - plan.add(Click.RIGHT_OUTSIDE, Click.OUTSIDE_SLOT); //drop one until goal is met + if (session.getGameMode() == GameMode.CREATIVE && inventory instanceof PlayerInventory) { + GeyserItemStack cursorItem = session.getPlayerInventory().getCursor(); + GeyserItemStack droppingItem = cursorItem.copy(); + // Subtract the cursor item by however much is being dropped + cursorItem.setAmount(cursorItem.getAmount() - dropAction.getCount()); + if (cursorItem.isEmpty()) { + // Cursor item no longer exists + session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY); + } + droppingItem.setAmount(dropAction.getCount()); + ClientCreativeInventoryActionPacket packet = new ClientCreativeInventoryActionPacket( + Click.OUTSIDE_SLOT, + droppingItem.getItemStack() + ); + System.out.println(packet.toString()); + session.sendDownstreamPacket(packet); + } else { + int sourceAmount = plan.getCursor().getAmount(); + if (dropAction.getCount() == sourceAmount) { //drop all + plan.add(Click.LEFT_OUTSIDE, Click.OUTSIDE_SLOT); + } else { //drop some + for (int i = 0; i < dropAction.getCount(); i++) { + plan.add(Click.RIGHT_OUTSIDE, Click.OUTSIDE_SLOT); //drop one until goal is met + } } } } else { //dropping from inventory @@ -395,6 +438,10 @@ public abstract class InventoryTranslator { case CRAFT_RESULTS_DEPRECATED: { break; } + case CRAFT_RECIPE_OPTIONAL: { + // Anvils and cartography tables will handle this + break; + } default: return rejectRequest(request); } @@ -578,7 +625,14 @@ public abstract class InventoryTranslator { Collections.singletonList(makeItemEntry(session, 0, session.getPlayerInventory().getCursor()))))); } else { int javaSlot = bedrockSlotToJava(transferAction.getDestination()); - inventory.setItem(javaSlot, GeyserItemStack.from(javaCreativeItem, session.getItemNetId().getAndIncrement())); + GeyserItemStack existingItem = inventory.getItem(javaSlot); + if (existingItem.getId() == javaCreativeItem.getId()) { + // Adding more to an existing item + existingItem.setAmount(existingItem.getAmount() + transferAction.getCount()); + javaCreativeItem = existingItem.getItemStack(); + } else { + inventory.setItem(javaSlot, GeyserItemStack.from(javaCreativeItem, session.getItemNetId().getAndIncrement())); + } ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( javaSlot, javaCreativeItem diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java new file mode 100644 index 000000000..27de329c6 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators; + +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.connector.inventory.AnvilContainer; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.PlayerInventory; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; + +public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { + public AnvilInventoryTranslator() { + super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, UIInventoryUpdater.INSTANCE); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.ANVIL_INPUT) { + return 0; + } + if (slotInfoData.getContainer() == ContainerSlotType.ANVIL_MATERIAL) { + return 1; + } + if (slotInfoData.getContainer() == ContainerSlotType.ANVIL_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { + return 2; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + switch (slot) { + case 0: + return new BedrockContainerSlot(ContainerSlotType.ANVIL_INPUT, 1); + case 1: + return new BedrockContainerSlot(ContainerSlotType.ANVIL_MATERIAL, 2); + case 2: + return new BedrockContainerSlot(ContainerSlotType.ANVIL_RESULT, 50); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int javaSlotToBedrock(int slot) { + switch (slot) { + case 0: + return 1; + case 1: + return 2; + case 2: + return 50; + } + return super.javaSlotToBedrock(slot); + } + + @Override + public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { + return new AnvilContainer(name, windowId, windowType, this.size, playerInventory); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java index 99ed2ecec..77eeee17b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java @@ -30,11 +30,11 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; import org.geysermc.connector.network.translators.inventory.SlotType; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslator { public CraftingInventoryTranslator() { - super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, CursorInventoryUpdater.INSTANCE); + super(10, "minecraft:crafting_table", ContainerType.WORKBENCH, UIInventoryUpdater.INSTANCE); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java index a8b13be4c..cfa024191 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java @@ -25,11 +25,195 @@ package org.geysermc.connector.network.translators.inventory.translators; +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; +import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; +import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import com.nukkitx.protocol.bedrock.packet.PlayerEnchantOptionsPacket; +import org.geysermc.connector.inventory.EnchantingContainer; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.PlayerInventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; +import org.geysermc.connector.network.translators.item.Enchantment; + +import java.util.Arrays; +import java.util.Collections; public class EnchantingInventoryTranslator extends AbstractBlockInventoryTranslator { public EnchantingInventoryTranslator() { - super(2, "minecraft:enchanting_table", ContainerType.ENCHANTMENT, CursorInventoryUpdater.INSTANCE); + super(2, "minecraft:enchanting_table", ContainerType.ENCHANTMENT, UIInventoryUpdater.INSTANCE); + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + int slotToUpdate; + EnchantingContainer enchantingInventory = (EnchantingContainer) inventory; + boolean shouldUpdate = false; + switch (key) { + case 0: + case 1: + case 2: + // Experience required + slotToUpdate = key; + enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setXpCost(value); + break; + case 4: + case 5: + case 6: + // Enchantment type + slotToUpdate = key - 4; + int index = value; + if (index != -1) { + Enchantment enchantment = Enchantment.getByJavaIdentifier("minecraft:" + JavaEnchantment.values()[index].name().toLowerCase()); + if (enchantment != null) { + // Convert the Java enchantment index to Bedrock's + index = enchantment.ordinal(); + } else { + index = -1; + } + } + enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setJavaEnchantIndex(value); + enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setBedrockEnchantIndex(index); + break; + case 7: + case 8: + case 9: + // Enchantment level + slotToUpdate = key - 7; + enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].setEnchantLevel(value); + shouldUpdate = true; // Java sends each property as its own packet, so let's only update after all properties have been sent + break; + default: + return; + } + if (shouldUpdate) { + enchantingInventory.getEnchantOptions()[slotToUpdate] = enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].build(session); + PlayerEnchantOptionsPacket packet = new PlayerEnchantOptionsPacket(); + packet.getOptions().addAll(Arrays.asList(enchantingInventory.getEnchantOptions())); + System.out.println(packet); + session.sendUpstreamPacket(packet); + } + } + + @Override + public boolean shouldHandleRequestFirst(StackRequestActionData action) { + return action.getType() == StackRequestActionType.CRAFT_RECIPE; + } + + @Override + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + // Client has requested an item to be enchanted + CraftRecipeStackRequestActionData craftRecipeData = (CraftRecipeStackRequestActionData) request.getActions()[0]; + EnchantingContainer enchantingInventory = (EnchantingContainer) inventory; + int javaSlot = -1; + for (int i = 0; i < enchantingInventory.getEnchantOptions().length; i++) { + EnchantOptionData enchantData = enchantingInventory.getEnchantOptions()[i]; + if (enchantData != null) { + if (craftRecipeData.getRecipeNetworkId() == enchantData.getEnchantNetId()) { + // Enchant net ID is how we differentiate between what item Bedrock wants + javaSlot = enchantingInventory.getGeyserEnchantOptions()[i].getJavaIndex(); + break; + } + } + } + if (javaSlot == -1) { + // Slot should be determined as 0, 1, or 2 + throw new RuntimeException("Cannot find enchant slot for item!"); + } + ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), javaSlot); + System.out.println(packet); + session.sendDownstreamPacket(packet); + return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.ENCHANTING_INPUT) { + return 0; + } + if (slotInfoData.getContainer() == ContainerSlotType.ENCHANTING_LAPIS) { + return 1; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.ENCHANTING_INPUT, 14); + } + if (slot == 1) { + return new BedrockContainerSlot(ContainerSlotType.ENCHANTING_LAPIS, 15); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int javaSlotToBedrock(int slot) { + if (slot == 0) { + return 14; + } + if (slot == 1) { + return 15; + } + return super.javaSlotToBedrock(slot); + } + + @Override + public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { + return new EnchantingContainer(name, windowId, windowType, this.size, playerInventory); + } + + /** + * Enchantments classified by their Java index + */ + public enum JavaEnchantment { + PROTECTION, + FIRE_PROTECTION, + FEATHER_FALLING, + BLAST_PROTECTION, + PROJECTILE_PROTECTION, + RESPIRATION, + AQUA_AFFINITY, + THORNS, + DEPTH_STRIDER, + FROST_WALKER, + BINDING_CURSE, + SOUL_SPEED, + SHARPNESS, + SMITE, + BANE_OF_ARTHROPODS, + KNOCKBACK, + FIRE_ASPECT, + LOOTING, + SWEEPING, + EFFICIENCY, + SILK_TOUCH, + UNBREAKING, + FORTUNE, + POWER, + PUNCH, + FLAME, + INFINITY, + LUCK_OF_THE_SEA, + LURE, + LOYALTY, + IMPALING, + RIPTIDE, + CHANNELING, + MULTISHOT, + QUICK_CHARGE, + PIERCING, + MENDING, + VANISHING_CURSE } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java index 684b25e80..14e3cf1eb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java @@ -29,11 +29,11 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; public class GrindstoneInventoryTranslator extends AbstractBlockInventoryTranslator { public GrindstoneInventoryTranslator() { - super(3, "minecraft:grindstone[face=floor,facing=north]", ContainerType.GRINDSTONE, CursorInventoryUpdater.INSTANCE); + super(3, "minecraft:grindstone[face=floor,facing=north]", ContainerType.GRINDSTONE, UIInventoryUpdater.INSTANCE); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java index e798157d6..33a4e8240 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java @@ -44,12 +44,11 @@ import org.geysermc.connector.network.translators.inventory.updater.InventoryUpd import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; public class MerchantInventoryTranslator extends BaseInventoryTranslator { - private final InventoryUpdater updater; public MerchantInventoryTranslator() { super(3); - this.updater = new UIInventoryUpdater(); + this.updater = UIInventoryUpdater.INSTANCE; } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java index fba8a8d6e..4d708bc3b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java @@ -29,11 +29,11 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; -import org.geysermc.connector.network.translators.inventory.updater.CursorInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslator { public SmithingInventoryTranslator() { - super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, CursorInventoryUpdater.INSTANCE); + super(3, "minecraft:smithing_table", ContainerType.SMITHING_TABLE, UIInventoryUpdater.INSTANCE); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java deleted file mode 100644 index 5d71f7e0b..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/CursorInventoryUpdater.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory.updater; - -import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; -import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; - -public class CursorInventoryUpdater extends InventoryUpdater { - public static final CursorInventoryUpdater INSTANCE = new CursorInventoryUpdater(); - - @Override - public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { - super.updateInventory(translator, session, inventory); - - for (int i = 0; i < translator.size; i++) { - final int bedrockSlot = translator.javaSlotToBedrock(i); - if (bedrockSlot == 50) - continue; - InventorySlotPacket slotPacket = new InventorySlotPacket(); - slotPacket.setContainerId(ContainerId.UI); - slotPacket.setSlot(bedrockSlot); - slotPacket.setItem(inventory.getItem(i).getItemData(session)); - session.sendUpstreamPacket(slotPacket); - } - } - - @Override - public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) { - if (super.updateSlot(translator, session, inventory, javaSlot)) - return true; - - InventorySlotPacket slotPacket = new InventorySlotPacket(); - slotPacket.setContainerId(ContainerId.UI); - slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); - slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); - session.sendUpstreamPacket(slotPacket); - return true; - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/UIInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/UIInventoryUpdater.java index 5100ddc99..1ebad489d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/UIInventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/UIInventoryUpdater.java @@ -30,9 +30,9 @@ import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.item.ItemTranslator; public class UIInventoryUpdater extends InventoryUpdater { + public static final UIInventoryUpdater INSTANCE = new UIInventoryUpdater(); @Override public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java index 97c4708ff..120439360 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java @@ -38,6 +38,7 @@ public class JavaWindowPropertyTranslator extends PacketTranslator { Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId()); if (inventory == null) From ff4f712eda405ff1b0e51a62f0d062d78695e024 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 23 Dec 2020 12:30:36 -0500 Subject: [PATCH 011/161] Implement beacon --- .../connector/inventory/BeaconContainer.java | 41 ++++++ .../connector/inventory/Inventory.java | 3 + .../inventory/InventoryTranslator.java | 2 +- .../holder/BlockInventoryHolder.java | 8 ++ .../BeaconInventoryTranslator.java | 134 ++++++++++++++++++ .../entity/BeaconBlockEntityTranslator.java | 41 ++++++ 6 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java new file mode 100644 index 000000000..70ecd84f8 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.inventory; + +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BeaconContainer extends Container { + private int primaryId; + private int secondaryId; + + public BeaconContainer(String title, int id, WindowType windowType, int size, PlayerInventory playerInventory) { + super(title, id, windowType, size, playerInventory); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java index f4aea4c90..fed94b0bf 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -50,6 +50,9 @@ public class Inventory { protected GeyserItemStack[] items; + /** + * 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; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 719a20922..a726e2d7a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -80,6 +80,7 @@ public abstract class InventoryTranslator { /* Specific Inventories */ put(WindowType.ANVIL, new AnvilInventoryTranslator()); + put(WindowType.BEACON, new BeaconInventoryTranslator()); put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); put(WindowType.CRAFTING, new CraftingInventoryTranslator()); put(WindowType.ENCHANTMENT, new EnchantingInventoryTranslator()); @@ -91,7 +92,6 @@ public abstract class InventoryTranslator { /* Generics */ put(WindowType.GENERIC_3X3, new GenericBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER)); put(WindowType.HOPPER, new GenericBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER)); - //put(WindowType.BEACON, new AbstractBlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO //put(WindowType.CARTOGRAPHY //put(WindowType.STONECUTTER diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index 339c8a545..1b4dfb8db 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -48,6 +48,11 @@ public class BlockInventoryHolder extends InventoryHolder { @Override public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + //TODO: Improve on this (for example, multiple block states). We need this for the beacon. + if (BlockTranslator.getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionPosition())) == blockId) { + inventory.setHolderPosition(session.getLastInteractionPosition()); + return; + } Vector3i position = session.getPlayerEntity().getPosition().toInt(); position = position.add(Vector3i.UP); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); @@ -82,6 +87,9 @@ public class BlockInventoryHolder extends InventoryHolder { @Override public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { Vector3i holderPos = inventory.getHolderPosition(); + if (holderPos.equals(session.getLastInteractionPosition())) { + return; + } Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); int realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java new file mode 100644 index 000000000..678d7e842 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators; + +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientSetBeaconEffectPacket; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.BeaconPaymentStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; +import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import org.geysermc.connector.inventory.BeaconContainer; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.PlayerInventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; + +import java.util.Collections; + +public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator { + public BeaconInventoryTranslator() { + super(1, "minecraft:beacon", ContainerType.BEACON, UIInventoryUpdater.INSTANCE); + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + //FIXME?: Beacon graphics look weird after inputting an item. This might be a Bedrock bug, since it resets to nothing + // on BDS + BeaconContainer beaconContainer = (BeaconContainer) inventory; + switch (key) { + case 0: + // Power - beacon doesn't use this, and uses the block position instead + break; + case 1: + beaconContainer.setPrimaryId(value == -1 ? 0 : value); + break; + case 2: + beaconContainer.setSecondaryId(value == -1 ? 0 : value); + break; + } + + // Send a block entity data packet update to the fake beacon inventory + Vector3i position = inventory.getHolderPosition(); + NbtMapBuilder builder = NbtMap.builder() + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .putInt("z", position.getZ()) + .putString("CustomName", inventory.getTitle()) + .putString("id", "Beacon") + .putInt("primary", beaconContainer.getPrimaryId()) + .putInt("secondary", beaconContainer.getSecondaryId()); + + BlockEntityDataPacket packet = new BlockEntityDataPacket(); + packet.setBlockPosition(position); + packet.setData(builder.build()); + System.out.println(packet.toString()); + session.sendUpstreamPacket(packet); + } + + @Override + public boolean shouldHandleRequestFirst(StackRequestActionData action) { + return action.getType() == StackRequestActionType.BEACON_PAYMENT; + } + + @Override + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + // Input a beacon payment + BeaconPaymentStackRequestActionData beaconPayment = (BeaconPaymentStackRequestActionData) request.getActions()[0]; + ClientSetBeaconEffectPacket packet = new ClientSetBeaconEffectPacket(beaconPayment.getPrimaryEffect(), beaconPayment.getSecondaryEffect()); + System.out.println(packet.toString()); + session.sendDownstreamPacket(packet); + return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.BEACON_PAYMENT) { + return 0; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.BEACON_PAYMENT, 27); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int javaSlotToBedrock(int slot) { + if (slot == 0) { + return 27; + } + return super.javaSlotToBedrock(slot); + } + + @Override + public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { + return new BeaconContainer(name, windowId, windowType, this.size, playerInventory); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java new file mode 100644 index 000000000..fca6af9b4 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.world.block.entity; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.NbtMapBuilder; + +@BlockEntity(name = "Beacon", regex = "beacon") +public class BeaconBlockEntityTranslator extends BlockEntityTranslator { + @Override + public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + int primary = getOrDefault(tag.get("Primary"), 0); + // The effects here generally map one-to-one Java <-> Bedrock. Only the newer ones get more complicated + builder.putInt("primary", primary == -1 ? 0 : primary); + int secondary = getOrDefault(tag.get("Secondary"), 0); + builder.putInt("secondary", secondary == -1 ? 0 : secondary); + } +} From 0f735a833050809b0e9b238111da49a176b570f5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 23 Dec 2020 12:37:55 -0500 Subject: [PATCH 012/161] Block entity cleanup --- .../world/block/entity/BannerBlockEntityTranslator.java | 2 +- .../world/block/entity/BeaconBlockEntityTranslator.java | 2 +- .../world/block/entity/BedBlockEntityTranslator.java | 2 +- .../translators/world/block/entity/BlockEntity.java | 6 ------ .../world/block/entity/BlockEntityTranslator.java | 7 +++---- .../world/block/entity/CampfireBlockEntityTranslator.java | 2 +- .../block/entity/CommandBlockBlockEntityTranslator.java | 2 +- .../block/entity/DoubleChestBlockEntityTranslator.java | 4 ++-- .../world/block/entity/EmptyBlockEntityTranslator.java | 2 +- .../block/entity/EndGatewayBlockEntityTranslator.java | 2 +- .../block/entity/JigsawBlockBlockEntityTranslator.java | 2 +- .../block/entity/ShulkerBoxBlockEntityTranslator.java | 2 +- .../world/block/entity/SignBlockEntityTranslator.java | 2 +- .../world/block/entity/SkullBlockEntityTranslator.java | 2 +- .../world/block/entity/SpawnerBlockEntityTranslator.java | 2 +- 15 files changed, 17 insertions(+), 24 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java index b59794796..18209508c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BannerBlockEntityTranslator.java @@ -31,7 +31,7 @@ import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.item.translators.BannerTranslator; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -@BlockEntity(name = "Banner", regex = "banner") +@BlockEntity(name = "Banner") public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @Override public boolean isBlock(int blockState) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java index fca6af9b4..f60b0daab 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java @@ -28,7 +28,7 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.NbtMapBuilder; -@BlockEntity(name = "Beacon", regex = "beacon") +@BlockEntity(name = "Beacon") public class BeaconBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java index 0067cc41f..a111274dc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedBlockEntityTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -@BlockEntity(name = "Bed", regex = "bed") +@BlockEntity(name = "Bed") public class BedBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @Override public boolean isBlock(int blockState) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java index 11bfe0ea4..3a1345ac2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntity.java @@ -36,10 +36,4 @@ public @interface BlockEntity { * @return the name of the block entity */ String name(); - - /** - * The search term used in BlockTranslator - * @return the search term used in BlockTranslator - */ - String regex(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java index 679636524..b9373b3c5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BlockEntityTranslator.java @@ -109,7 +109,7 @@ public abstract class BlockEntityTranslator { int y = ((IntTag) tag.getValue().get("y")).getValue(); int z = ((IntTag) tag.getValue().get("z")).getValue(); - NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder(); + NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z); translateTag(tagBuilder, tag, blockState); return tagBuilder.build(); } @@ -123,13 +123,12 @@ public abstract class BlockEntityTranslator { return tag; } - protected NbtMap getConstantBedrockTag(String bedrockId, int x, int y, int z) { + protected NbtMapBuilder getConstantBedrockTag(String bedrockId, int x, int y, int z) { return NbtMap.builder() .putInt("x", x) .putInt("y", y) .putInt("z", z) - .putString("id", bedrockId) - .build(); + .putString("id", bedrockId); } @SuppressWarnings("unchecked") diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java index 3e4f9fb90..e41bc5ff9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CampfireBlockEntityTranslator.java @@ -32,7 +32,7 @@ import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; -@BlockEntity(name = "Campfire", regex = "campfire") +@BlockEntity(name = "Campfire") public class CampfireBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java index 1eb50ffe7..99e89f273 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/CommandBlockBlockEntityTranslator.java @@ -30,7 +30,7 @@ import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.network.translators.chat.MessageTranslator; -@BlockEntity(name = "CommandBlock", regex = "command_block") +@BlockEntity(name = "CommandBlock") public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java index 47bcf4897..a5b67ecb8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/DoubleChestBlockEntityTranslator.java @@ -36,7 +36,7 @@ import org.geysermc.connector.utils.BlockEntityUtils; /** * Chests have more block entity properties in Bedrock, which is solved by implementing the BedrockOnlyBlockEntity */ -@BlockEntity(name = "Chest", regex = "chest") +@BlockEntity(name = "Chest") public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { @Override public boolean isBlock(int blockState) { @@ -46,7 +46,7 @@ public class DoubleChestBlockEntityTranslator extends BlockEntityTranslator impl @Override public void updateBlock(GeyserSession session, int blockState, Vector3i position) { CompoundTag javaTag = getConstantJavaTag("chest", position.getX(), position.getY(), position.getZ()); - NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId("chest"), position.getX(), position.getY(), position.getZ()).toBuilder(); + NbtMapBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId("chest"), position.getX(), position.getY(), position.getZ()); translateTag(tagBuilder, javaTag, blockState); BlockEntityUtils.updateBlockEntity(session, tagBuilder.build(), position); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java index 3926b8664..0b8839055 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EmptyBlockEntityTranslator.java @@ -28,7 +28,7 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.NbtMapBuilder; -@BlockEntity(name = "Empty", regex = "") +@BlockEntity(name = "Empty") public class EmptyBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java index 0bf588226..03689ac9e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java @@ -35,7 +35,7 @@ import it.unimi.dsi.fastutil.ints.IntList; import java.util.LinkedHashMap; -@BlockEntity(name = "EndGateway", regex = "end_gateway") +@BlockEntity(name = "EndGateway") public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java index 4fcdfe54d..6f3e7b3d7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/JigsawBlockBlockEntityTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.nukkitx.nbt.NbtMapBuilder; -@BlockEntity(name = "JigsawBlock", regex = "jigsaw") +@BlockEntity(name = "JigsawBlock") public class JigsawBlockBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java index 69fa10845..0eb27da56 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java @@ -29,7 +29,7 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -@BlockEntity(name = "ShulkerBox", regex = "shulker_box") +@BlockEntity(name = "ShulkerBox") public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java index a9641d772..31397086a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java @@ -30,7 +30,7 @@ import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.utils.SignUtils; -@BlockEntity(name = "Sign", regex = "sign") +@BlockEntity(name = "Sign") public class SignBlockEntityTranslator extends BlockEntityTranslator { /** * Maps a color stored in a sign's Color tag to a Bedrock Edition formatting code. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java index 5da9c0e0d..d8249ac50 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SkullBlockEntityTranslator.java @@ -46,7 +46,7 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; -@BlockEntity(name = "Skull", regex = "skull") +@BlockEntity(name = "Skull") public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { public static boolean ALLOW_CUSTOM_SKULLS; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java index 38507f54a..4b8b344aa 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SpawnerBlockEntityTranslator.java @@ -30,7 +30,7 @@ import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.entity.type.EntityType; -@BlockEntity(name = "MobSpawner", regex = "mob_spawner") +@BlockEntity(name = "MobSpawner") public class SpawnerBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { From 4d80edf6d9139914a5233493325f731038c93aba Mon Sep 17 00:00:00 2001 From: D3ATHBRINGER13 Date: Thu, 24 Dec 2020 00:48:21 +0000 Subject: [PATCH 013/161] Initial loom funtionality --- .../inventory/InventoryTranslator.java | 3 +- .../translators/LoomInventoryTranslator.java | 85 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index a726e2d7a..1dc8fa7d9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -93,9 +93,10 @@ public abstract class InventoryTranslator { put(WindowType.GENERIC_3X3, new GenericBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER)); put(WindowType.HOPPER, new GenericBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER)); + /* Workstations */ //put(WindowType.CARTOGRAPHY //put(WindowType.STONECUTTER - //put(WindowType.LOOM + put(WindowType.LOOM, new LoomInventoryTranslator()); //put(WindowType. } }; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java new file mode 100644 index 000000000..5aa716bda --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; + +public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { + public LoomInventoryTranslator() { + super(4, "minecraft:loom[facing=north]", ContainerType.LOOM, UIInventoryUpdater.INSTANCE); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.LOOM_INPUT) { + return 0; + } + if (slotInfoData.getContainer() == ContainerSlotType.LOOM_DYE) { + return 1; + } + if (slotInfoData.getContainer() == ContainerSlotType.LOOM_MATERIAL) { + return 2; + } + if (slotInfoData.getContainer() == ContainerSlotType.LOOM_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { + return 3; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + switch (slot) { + case 0: + return new BedrockContainerSlot(ContainerSlotType.LOOM_INPUT, 9); + case 1: + return new BedrockContainerSlot(ContainerSlotType.LOOM_DYE, 10); + case 2: + return new BedrockContainerSlot(ContainerSlotType.LOOM_MATERIAL, 11); + case 3: + return new BedrockContainerSlot(ContainerSlotType.LOOM_RESULT, 50); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int javaSlotToBedrock(int slot) { + switch (slot) { + case 0: + return 9; + case 1: + return 10; + case 2: + return 11; + case 3: + return 50; + } + return super.javaSlotToBedrock(slot); + } +} From 6ae81cce527e0446d09dbd78eca1b988f680a001 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 23 Dec 2020 22:53:54 -0500 Subject: [PATCH 014/161] Full banner loom support --- .../inventory/InventoryTranslator.java | 10 +- .../BeaconInventoryTranslator.java | 2 +- .../EnchantingInventoryTranslator.java | 2 +- .../translators/LoomInventoryTranslator.java | 92 +++++++++++++++++++ 4 files changed, 99 insertions(+), 7 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 1dc8fa7d9..bde448ab9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -85,6 +85,7 @@ public abstract class InventoryTranslator { put(WindowType.CRAFTING, new CraftingInventoryTranslator()); put(WindowType.ENCHANTMENT, new EnchantingInventoryTranslator()); put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); + put(WindowType.LOOM, new LoomInventoryTranslator()); put(WindowType.MERCHANT, new MerchantInventoryTranslator()); put(WindowType.SHULKER_BOX, new ShulkerInventoryTranslator()); put(WindowType.SMITHING, new SmithingInventoryTranslator()); @@ -93,10 +94,9 @@ public abstract class InventoryTranslator { put(WindowType.GENERIC_3X3, new GenericBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER)); put(WindowType.HOPPER, new GenericBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER)); - /* Workstations */ + /* todo */ //put(WindowType.CARTOGRAPHY //put(WindowType.STONECUTTER - put(WindowType.LOOM, new LoomInventoryTranslator()); //put(WindowType. } }; @@ -122,12 +122,12 @@ public abstract class InventoryTranslator { * Should be overrided if this request matches a certain criteria and shouldn't be treated normally. * E.G. anvil renaming or enchanting */ - public boolean shouldHandleRequestFirst(StackRequestActionData action) { + public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { return false; } /** - * If {@link #shouldHandleRequestFirst(StackRequestActionData)} returns true, this will be called + * If {@link #shouldHandleRequestFirst(StackRequestActionData, Inventory)} returns true, this will be called */ public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { return null; @@ -138,7 +138,7 @@ public abstract class InventoryTranslator { for (ItemStackRequestPacket.Request request : requests) { if (request.getActions().length > 0) { StackRequestActionData firstAction = request.getActions()[0]; - if (shouldHandleRequestFirst(firstAction)) { + if (shouldHandleRequestFirst(firstAction, inventory)) { // Some special request that shouldn't be processed normally responsePacket.getEntries().add(translateSpecialRequest(session, inventory, request)); } else if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE || firstAction.getType() == StackRequestActionType.CRAFT_RECIPE_AUTO) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java index 678d7e842..8ffc3e1a9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java @@ -89,7 +89,7 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator } @Override - public boolean shouldHandleRequestFirst(StackRequestActionData action) { + public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { return action.getType() == StackRequestActionType.BEACON_PAYMENT; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java index cfa024191..ea836e3bd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java @@ -105,7 +105,7 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla } @Override - public boolean shouldHandleRequestFirst(StackRequestActionData action) { + public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { return action.getType() == StackRequestActionType.CRAFT_RECIPE; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java index 5aa716bda..db5fc963d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java @@ -25,17 +25,109 @@ package org.geysermc.connector.network.translators.inventory.translators; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; +import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; +import java.util.List; + public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { + /** + * A map of Bedrock patterns to Java index. Used to request for a specific banner pattern. + */ + private static final Object2IntMap PATTERN_TO_INDEX = new Object2IntOpenHashMap<>(); + + static { + // Added from left-to-right then up-to-down in the order Java presents it + int index = 1; + PATTERN_TO_INDEX.put("bl", index++); + PATTERN_TO_INDEX.put("br", index++); + PATTERN_TO_INDEX.put("tl", index++); + PATTERN_TO_INDEX.put("tr", index++); + PATTERN_TO_INDEX.put("bs", index++); + PATTERN_TO_INDEX.put("ts", index++); + PATTERN_TO_INDEX.put("ls", index++); + PATTERN_TO_INDEX.put("rs", index++); + PATTERN_TO_INDEX.put("cs", index++); + PATTERN_TO_INDEX.put("ms", index++); + PATTERN_TO_INDEX.put("drs", index++); + PATTERN_TO_INDEX.put("dls", index++); + PATTERN_TO_INDEX.put("ss", index++); + PATTERN_TO_INDEX.put("cr", index++); + PATTERN_TO_INDEX.put("sc", index++); + PATTERN_TO_INDEX.put("bt", index++); + PATTERN_TO_INDEX.put("tt", index++); + PATTERN_TO_INDEX.put("bts", index++); + PATTERN_TO_INDEX.put("tts", index++); + PATTERN_TO_INDEX.put("ld", index++); + PATTERN_TO_INDEX.put("rd", index++); + PATTERN_TO_INDEX.put("lud", index++); + PATTERN_TO_INDEX.put("rud", index++); + PATTERN_TO_INDEX.put("mc", index++); + PATTERN_TO_INDEX.put("mr", index++); + PATTERN_TO_INDEX.put("vh", index++); + PATTERN_TO_INDEX.put("hh", index++); + PATTERN_TO_INDEX.put("vhr", index++); + PATTERN_TO_INDEX.put("hhb", index++); + PATTERN_TO_INDEX.put("bo", index++); + index++; // Bordure indented, does not appear to exist in Bedrock? + PATTERN_TO_INDEX.put("gra", index++); + PATTERN_TO_INDEX.put("gru", index); + // Bricks do not appear to be a pattern on Bedrock, either + } + public LoomInventoryTranslator() { super(4, "minecraft:loom[facing=north]", ContainerType.LOOM, UIInventoryUpdater.INSTANCE); } + @Override + public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { + // If the LOOM_MATERIAL slot is not empty, we are crafting a pattern that does not come from an item + return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED && inventory.getItem(2).isEmpty(); + } + + @Override + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + // TODO: I anticipate this will be changed in the future to use something non-deprecated. Keep an eye out. + // Also TODO: Shift-clicking doesn't work here. + StackRequestActionData data = request.getActions()[1]; + if (!(data instanceof CraftResultsDeprecatedStackRequestActionData)) { + return rejectRequest(request); + } + CraftResultsDeprecatedStackRequestActionData craftData = (CraftResultsDeprecatedStackRequestActionData) data; + // Get the patterns compound tag + List blockEntityTag = craftData.getResultItems()[0].getTag().getList("Patterns", NbtType.COMPOUND); + // Get the pattern that the Bedrock client requests - the last pattern in the Patterns list + String pattern = blockEntityTag.get(blockEntityTag.size() - 1).getString("Pattern"); + // Get the Java index of this pattern + int index = PATTERN_TO_INDEX.getOrDefault(pattern, -1); + if (index == -1) { + return rejectRequest(request); + } + // Java's formula: 4 * row + col + // And the Java loom window has a fixed row/width of four + // So... Number / 4 = row (so we don't have to bother there), and number % 4 is our column, which leads us back to our index. :) + ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), index); + System.out.println(packet); + session.sendDownstreamPacket(packet); + + return translateRequest(session, inventory, request); + } + @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { if (slotInfoData.getContainer() == ContainerSlotType.LOOM_INPUT) { From 617a1216d5f6226b23344a36f617d433462cd24c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 24 Dec 2020 11:23:47 -0500 Subject: [PATCH 015/161] Initial work on stonecutters --- .../network/session/GeyserSession.java | 19 +-- .../inventory/InventoryTranslator.java | 4 +- .../StonecutterInventoryTranslator.java | 118 ++++++++++++++++++ .../java/JavaDeclareRecipesTranslator.java | 23 ++++ 4 files changed, 153 insertions(+), 11 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 9f29acbb5..5d0e26040 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -32,8 +32,8 @@ import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.MinecraftProtocol; import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; +import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; @@ -53,6 +53,7 @@ import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.packet.*; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -93,15 +94,8 @@ import java.net.InetSocketAddress; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @Getter @@ -218,6 +212,13 @@ public class GeyserSession implements CommandSender { @Setter private Int2ObjectMap craftingRecipes; + /** + * Saves a list of all stonecutter recipes, for use in a stonecutter inventory. + * The key is the Java ID of the item; the values are all the possible outputs' Java IDs + */ + @Setter + private Int2ObjectMap stonecutterRecipes; + /** * The current attack speed of the player. Used for sending proper cooldown timings. * Setting a default fixes cooldowns not showing up on a fresh world. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index bde448ab9..d2fb12a01 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -89,6 +89,7 @@ public abstract class InventoryTranslator { put(WindowType.MERCHANT, new MerchantInventoryTranslator()); put(WindowType.SHULKER_BOX, new ShulkerInventoryTranslator()); put(WindowType.SMITHING, new SmithingInventoryTranslator()); + put(WindowType.STONECUTTER, new StonecutterInventoryTranslator()); /* Generics */ put(WindowType.GENERIC_3X3, new GenericBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER)); @@ -96,8 +97,7 @@ public abstract class InventoryTranslator { /* todo */ //put(WindowType.CARTOGRAPHY - //put(WindowType.STONECUTTER - //put(WindowType. + // horse } }; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java new file mode 100644 index 000000000..dd6c3b365 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; +import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import it.unimi.dsi.fastutil.ints.IntSet; +import org.geysermc.connector.inventory.GeyserItemStack; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; +import org.geysermc.connector.network.translators.item.ItemTranslator; + +import java.util.stream.Collectors; + +public class StonecutterInventoryTranslator extends AbstractBlockInventoryTranslator { + public StonecutterInventoryTranslator() { + super(2, "minecraft:stonecutter[facing=north]", ContainerType.STONECUTTER, UIInventoryUpdater.INSTANCE); + } + + @Override + public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { + return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED; + } + + @Override + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + // TODO: Also surely to change in the future + StackRequestActionData data = request.getActions()[1]; + if (!(data instanceof CraftResultsDeprecatedStackRequestActionData)) { + return rejectRequest(request); + } + CraftResultsDeprecatedStackRequestActionData craftData = (CraftResultsDeprecatedStackRequestActionData) data; + // Get the ID of the item we are cutting + int id = inventory.getItem(0).getId(); + // Look up all possible options of cutting from this ID + IntSet results = session.getStonecutterRecipes().get(id); + if (results == null) { + return rejectRequest(request); + } + ItemStack javaOutput = ItemTranslator.translateToJava(craftData.getResultItems()[0]); + // TODO bruh + // Getting the index of the item in the Java stonecutter list + // We do need to sort them by their Java ID to preserve index, though + int index = results.stream().sorted().collect(Collectors.toList()).indexOf(javaOutput.getId()); + ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), index + 1); + System.out.println(packet.toString()); + session.sendDownstreamPacket(packet); + // We don't know there is an output here, so we tell ourselves that there is + inventory.setItem(1, GeyserItemStack.from(javaOutput, session.getItemNetId().incrementAndGet())); + return translateRequest(session, inventory, request); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.STONECUTTER_INPUT) { + return 0; + } + if (slotInfoData.getContainer() == ContainerSlotType.STONECUTTER_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { + return 1; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0) { + return new BedrockContainerSlot(ContainerSlotType.STONECUTTER_INPUT, 3); + } + if (slot == 1) { + return new BedrockContainerSlot(ContainerSlotType.STONECUTTER_RESULT, 50); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int javaSlotToBedrock(int slot) { + if (slot == 0) { + return 3; + } + if (slot == 1) { + return 50; + } + return super.javaSlotToBedrock(slot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index 824b5acde..c475396c5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -25,10 +25,12 @@ package org.geysermc.connector.network.translators.java; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; +import com.github.steveice10.mc.protocol.data.game.recipe.data.StoneCuttingRecipeData; import com.github.steveice10.mc.protocol.packet.ingame.server.ServerDeclareRecipesPacket; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; @@ -56,6 +58,7 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator recipeMap = new Int2ObjectOpenHashMap<>(); + Int2ObjectMap stonecutterRecipeMap = new Int2ObjectOpenHashMap<>(); CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); craftingDataPacket.setCleanRecipes(true); for (Recipe recipe : packet.getRecipes()) { @@ -136,11 +139,31 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator Date: Thu, 24 Dec 2020 18:29:25 -0500 Subject: [PATCH 016/161] Stonecutter fixed; Loom improved --- .../network/session/GeyserSession.java | 6 +- .../inventory/InventoryTranslator.java | 24 ++++++-- .../EnchantingInventoryTranslator.java | 2 +- .../translators/LoomInventoryTranslator.java | 51 +++++++++++++++-- .../StonecutterInventoryTranslator.java | 28 ++++++---- .../item/translators/BannerTranslator.java | 5 +- .../java/JavaDeclareRecipesTranslator.java | 55 ++++++++++++------- 7 files changed, 129 insertions(+), 42 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 5d0e26040..880e87128 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -53,7 +53,7 @@ import com.nukkitx.protocol.bedrock.data.command.CommandPermission; import com.nukkitx.protocol.bedrock.packet.*; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; @@ -214,10 +214,10 @@ public class GeyserSession implements CommandSender { /** * Saves a list of all stonecutter recipes, for use in a stonecutter inventory. - * The key is the Java ID of the item; the values are all the possible outputs' Java IDs + * The key is the Java ID of the item; the values are all the possible outputs' Java IDs sorted by their string identifier */ @Setter - private Int2ObjectMap stonecutterRecipes; + private Int2ObjectMap stonecutterRecipes; /** * The current attack speed of the player. Used for sending proper cooldown timings. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index d2fb12a01..b3f2475d8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -118,6 +118,16 @@ public abstract class InventoryTranslator { public abstract SlotType getSlotType(int javaSlot); public abstract Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory); + /** + * Should be overwritten in cases where specific inventories should reject an item being in a specific spot. + * For examples, looms use this to reject items that are dyes in Bedrock but not in Java. + * + * @return true if this transfer should be rejected + */ + public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaDestinationSlot) { + return false; + } + /** * Should be overrided if this request matches a certain criteria and shouldn't be treated normally. * E.G. anvil renaming or enchanting @@ -178,12 +188,18 @@ public abstract class InventoryTranslator { return rejectRequest(request); } + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + + if (shouldRejectItemPlace(session, inventory, destSlot)) { + // This item would not be here in Java + return rejectRequest(request, false); + } + if (isCursor(transferAction.getSource()) && isCursor(transferAction.getDestination())) { //??? return rejectRequest(request); } else if (session.getGameMode().equals(GameMode.CREATIVE) && inventory instanceof PlayerInventory) { // TODO: does the Java server use this stuff all the time in creative? // Creative acts a little differently because it just edits slots int sourceSlot = bedrockSlotToJava(transferAction.getSource()); - int destSlot = bedrockSlotToJava(transferAction.getDestination()); boolean sourceIsCursor = isCursor(transferAction.getSource()); boolean destIsCursor = isCursor(transferAction.getDestination()); @@ -249,7 +265,6 @@ public abstract class InventoryTranslator { } else if (isCursor(transferAction.getSource())) { //releasing cursor int sourceAmount = cursor.getAmount(); - int destSlot = bedrockSlotToJava(transferAction.getDestination()); if (transferAction.getCount() == sourceAmount) { //release all plan.add(Click.LEFT, destSlot); } else { //release some @@ -279,7 +294,9 @@ public abstract class InventoryTranslator { if (transferAction.getCount() != sourceAmount) { //TODO: handle partially picking up into non-empty cursor (temp slot) return rejectRequest(request); } - plan.add(Click.LEFT, sourceSlot); //release cursor onto source slot + if (getSlotType(sourceSlot).equals(SlotType.NORMAL)) { + plan.add(Click.LEFT, sourceSlot); //release cursor onto source slot + } plan.add(Click.LEFT, sourceSlot); //pickup combined cursor and source } } else { //transfer from one slot to another @@ -288,7 +305,6 @@ public abstract class InventoryTranslator { } int sourceSlot = bedrockSlotToJava(transferAction.getSource()); int sourceAmount = plan.getItem(sourceSlot).getAmount(); - int destSlot = bedrockSlotToJava(transferAction.getDestination()); if (transferAction.getCount() == sourceAmount) { //transfer all plan.add(Click.LEFT, sourceSlot); //pickup source plan.add(Click.LEFT, destSlot); //let go of all items and done diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java index ea836e3bd..c8ae8e2df 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java @@ -127,7 +127,7 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla } if (javaSlot == -1) { // Slot should be determined as 0, 1, or 2 - throw new RuntimeException("Cannot find enchant slot for item!"); + return rejectRequest(request); } ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), javaSlot); System.out.println(packet); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java index db5fc963d..bacc83a02 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java @@ -26,6 +26,8 @@ package org.geysermc.connector.network.translators.inventory.translators; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; @@ -38,11 +40,15 @@ import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.item.translators.BannerTranslator; +import java.util.Collections; import java.util.List; public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { @@ -94,6 +100,20 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { super(4, "minecraft:loom[facing=north]", ContainerType.LOOM, UIInventoryUpdater.INSTANCE); } + @Override + public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaDestinationSlot) { + if (javaDestinationSlot != 1) { + return false; + } + GeyserItemStack itemStack = session.getPlayerInventory().getCursor(); + if (itemStack.isEmpty()) { + return false; + } + + // Reject the item if Bedrock is attempting to put in a dye that is not a dye in Java Edition + return !ItemRegistry.getItem(itemStack.getItemStack()).getJavaIdentifier().contains("_dye"); + } + @Override public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { // If the LOOM_MATERIAL slot is not empty, we are crafting a pattern that does not come from an item @@ -103,18 +123,17 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { @Override public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { // TODO: I anticipate this will be changed in the future to use something non-deprecated. Keep an eye out. - // Also TODO: Shift-clicking doesn't work here. StackRequestActionData data = request.getActions()[1]; if (!(data instanceof CraftResultsDeprecatedStackRequestActionData)) { return rejectRequest(request); } CraftResultsDeprecatedStackRequestActionData craftData = (CraftResultsDeprecatedStackRequestActionData) data; // Get the patterns compound tag - List blockEntityTag = craftData.getResultItems()[0].getTag().getList("Patterns", NbtType.COMPOUND); + List newblockEntityTag = craftData.getResultItems()[0].getTag().getList("Patterns", NbtType.COMPOUND); // Get the pattern that the Bedrock client requests - the last pattern in the Patterns list - String pattern = blockEntityTag.get(blockEntityTag.size() - 1).getString("Pattern"); + NbtMap pattern = newblockEntityTag.get(newblockEntityTag.size() - 1); // Get the Java index of this pattern - int index = PATTERN_TO_INDEX.getOrDefault(pattern, -1); + int index = PATTERN_TO_INDEX.getOrDefault(pattern.getString("Pattern"), -1); if (index == -1) { return rejectRequest(request); } @@ -124,6 +143,30 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), index); System.out.println(packet); session.sendDownstreamPacket(packet); + GeyserItemStack inputCopy = inventory.getItem(0).copy(); + inputCopy.setNetId(session.getItemNetId().incrementAndGet()); + // Add the pattern manually, for better item synchronization + if (inputCopy.getNbt() == null) { + inputCopy.setNbt(new CompoundTag("")); + } + CompoundTag blockEntityTag = inputCopy.getNbt().get("BlockEntityTag"); + CompoundTag javaBannerPattern = BannerTranslator.getJavaBannerPattern(pattern); + if (blockEntityTag != null) { + ListTag patternsList = blockEntityTag.get("Patterns"); + if (patternsList != null) { + patternsList.add(javaBannerPattern); + } else { + patternsList = new ListTag("Patterns", Collections.singletonList(javaBannerPattern)); + blockEntityTag.put(patternsList); + } + } else { + blockEntityTag = new CompoundTag("BlockEntityTag"); + ListTag patternsList = new ListTag("Patterns", Collections.singletonList(javaBannerPattern)); + blockEntityTag.put(patternsList); + inputCopy.getNbt().put(blockEntityTag); + } + // Set the new item as the output + inventory.setItem(3, inputCopy); return translateRequest(session, inventory, request); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java index dd6c3b365..7bac3c650 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java @@ -35,16 +35,15 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; -import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.IntList; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.SlotType; import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; import org.geysermc.connector.network.translators.item.ItemTranslator; -import java.util.stream.Collectors; - public class StonecutterInventoryTranslator extends AbstractBlockInventoryTranslator { public StonecutterInventoryTranslator() { super(2, "minecraft:stonecutter[facing=north]", ContainerType.STONECUTTER, UIInventoryUpdater.INSTANCE); @@ -66,20 +65,21 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl // Get the ID of the item we are cutting int id = inventory.getItem(0).getId(); // Look up all possible options of cutting from this ID - IntSet results = session.getStonecutterRecipes().get(id); + IntList results = session.getStonecutterRecipes().get(id); if (results == null) { return rejectRequest(request); } + System.out.println(id + " " + results); ItemStack javaOutput = ItemTranslator.translateToJava(craftData.getResultItems()[0]); - // TODO bruh + System.out.println(javaOutput); // Getting the index of the item in the Java stonecutter list - // We do need to sort them by their Java ID to preserve index, though - int index = results.stream().sorted().collect(Collectors.toList()).indexOf(javaOutput.getId()); - ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), index + 1); + ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), results.indexOf(javaOutput.getId())); System.out.println(packet.toString()); session.sendDownstreamPacket(packet); - // We don't know there is an output here, so we tell ourselves that there is - inventory.setItem(1, GeyserItemStack.from(javaOutput, session.getItemNetId().incrementAndGet())); + if (inventory.getItem(1).getId() != javaOutput.getId()) { + // We don't know there is an output here, so we tell ourselves that there is + inventory.setItem(1, GeyserItemStack.from(javaOutput, session.getItemNetId().incrementAndGet())); + } return translateRequest(session, inventory, request); } @@ -115,4 +115,12 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl } return super.javaSlotToBedrock(slot); } + + @Override + public SlotType getSlotType(int javaSlot) { + if (javaSlot == 1) { + return SlotType.OUTPUT; + } + return super.getSlotType(javaSlot); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java index 5e5bc3542..1870a1279 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java @@ -32,6 +32,7 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; @@ -181,6 +182,7 @@ public class BannerTranslator extends ItemTranslator { @Override public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + GeyserConnector.getInstance().getLogger().warning(itemEntry.toString()); if (itemData.getTag() == null) { return super.translateToJava(itemData, itemEntry); } @@ -195,13 +197,14 @@ public class BannerTranslator extends ItemTranslator { blockEntityTag.put(OMINOUS_BANNER_PATTERN); itemStack.getNbt().put(blockEntityTag); - } else if (nbtTag.containsKey("Patterns", NbtType.COMPOUND)) { + } else if (nbtTag.containsKey("Patterns", NbtType.LIST)) { List patterns = nbtTag.getList("Patterns", NbtType.COMPOUND); CompoundTag blockEntityTag = new CompoundTag("BlockEntityTag"); blockEntityTag.put(convertBannerPattern(patterns)); itemStack.getNbt().put(blockEntityTag); + itemStack.getNbt().remove("Patterns"); // Remove the old Bedrock patterns list } return itemStack; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index c475396c5..e74a82324 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -36,10 +36,7 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.*; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import org.geysermc.connector.network.session.GeyserSession; @@ -58,7 +55,7 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator recipeMap = new Int2ObjectOpenHashMap<>(); - Int2ObjectMap stonecutterRecipeMap = new Int2ObjectOpenHashMap<>(); + Int2ObjectMap> unsortedStonecutterData = new Int2ObjectOpenHashMap<>(); CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); craftingDataPacket.setCleanRecipes(true); for (Recipe recipe : packet.getRecipes()) { @@ -141,26 +138,46 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator data = unsortedStonecutterData.get(ingredient.getId()); + if (data == null) { + data = new ArrayList<>(); + unsortedStonecutterData.put(ingredient.getId(), data); } - // Add the ingredient as the key and all possible values as the value - outputs.add(stoneCuttingData.getResult().getId()); + data.add(stoneCuttingData); + // Save for processing after all recipes have been received } } } craftingDataPacket.getPotionMixData().addAll(PotionMixRegistry.POTION_MIXES); + + Int2ObjectMap stonecutterRecipeMap = new Int2ObjectOpenHashMap<>(); + for (Int2ObjectMap.Entry> data : unsortedStonecutterData.int2ObjectEntrySet()) { + data.getValue().sort(Comparator.comparing((stoneCuttingRecipeData -> + // Sort the list by each output item's Java identifier - this is how it's sorted on Java, and therefore + // We can get the correct order for button pressing + ItemRegistry.getItem(stoneCuttingRecipeData.getResult()).getJavaIdentifier()))); + // Now that it's sorted, let's translate these recipes + for (StoneCuttingRecipeData stoneCuttingData : data.getValue()) { + // As of 1.16.4, all stonecutter recipes have one ingredient option + ItemStack ingredient = stoneCuttingData.getIngredient().getOptions()[0]; + ItemData input = ItemTranslator.translateToBedrock(session, ingredient); + ItemData output = ItemTranslator.translateToBedrock(session, stoneCuttingData.getResult()); + UUID uuid = UUID.randomUUID(); + // We need to register stonecutting recipes so they show up on Bedrock + craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), + Collections.singletonList(input), Collections.singletonList(output), uuid, "stonecutter", 0, netId++)); + // Save the recipe list for reference when crafting + IntList outputs = stonecutterRecipeMap.get(ingredient.getId()); + if (outputs == null) { + outputs = new IntArrayList(); + // Add the ingredient as the key and all possible values as the value + stonecutterRecipeMap.put(ingredient.getId(), outputs); + } + outputs.add(stoneCuttingData.getResult().getId()); + } + } + session.sendUpstreamPacket(craftingDataPacket); session.setCraftingRecipes(recipeMap); session.setStonecutterRecipes(stonecutterRecipeMap); From c7fade295ea93d31752f38dcf879ad70ce8c23d5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 24 Dec 2020 20:43:24 -0500 Subject: [PATCH 017/161] Add swap support for creative mode; start on cartography table --- .../inventory/InventoryTranslator.java | 53 +++++++++-- .../CartographyInventoryTranslator.java | 95 +++++++++++++++++++ .../translators/LoomInventoryTranslator.java | 8 +- 3 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index b3f2475d8..3e5bf0c67 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -82,6 +82,7 @@ public abstract class InventoryTranslator { put(WindowType.ANVIL, new AnvilInventoryTranslator()); put(WindowType.BEACON, new BeaconInventoryTranslator()); put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); + put(WindowType.CARTOGRAPHY, new CartographyInventoryTranslator()); put(WindowType.CRAFTING, new CraftingInventoryTranslator()); put(WindowType.ENCHANTMENT, new EnchantingInventoryTranslator()); put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); @@ -96,7 +97,6 @@ public abstract class InventoryTranslator { put(WindowType.HOPPER, new GenericBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER)); /* todo */ - //put(WindowType.CARTOGRAPHY // horse } }; @@ -122,9 +122,11 @@ public abstract class InventoryTranslator { * Should be overwritten in cases where specific inventories should reject an item being in a specific spot. * For examples, looms use this to reject items that are dyes in Bedrock but not in Java. * + * javaSourceSlot will be -1 if the cursor is the source + * * @return true if this transfer should be rejected */ - public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaDestinationSlot) { + public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaSourceSlot, int javaDestinationSlot) { return false; } @@ -188,9 +190,10 @@ public abstract class InventoryTranslator { return rejectRequest(request); } + int sourceSlot = bedrockSlotToJava(transferAction.getSource()); int destSlot = bedrockSlotToJava(transferAction.getDestination()); - if (shouldRejectItemPlace(session, inventory, destSlot)) { + if (shouldRejectItemPlace(session, inventory, isCursor(transferAction.getSource()) ? -1 :sourceSlot, destSlot)) { // This item would not be here in Java return rejectRequest(request, false); } @@ -199,7 +202,6 @@ public abstract class InventoryTranslator { return rejectRequest(request); } else if (session.getGameMode().equals(GameMode.CREATIVE) && inventory instanceof PlayerInventory) { // TODO: does the Java server use this stuff all the time in creative? // Creative acts a little differently because it just edits slots - int sourceSlot = bedrockSlotToJava(transferAction.getSource()); boolean sourceIsCursor = isCursor(transferAction.getSource()); boolean destIsCursor = isCursor(transferAction.getDestination()); @@ -228,6 +230,15 @@ public abstract class InventoryTranslator { affectedSlots.add(destSlot); break; } + } else { + // Delete the source since we're moving it + inventory.setItem(sourceSlot, GeyserItemStack.EMPTY); + ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( + sourceSlot, + new ItemStack(0) + ); + session.sendDownstreamPacket(creativeActionPacket); + affectedSlots.add(sourceSlot); } // Update the item count with however much the client took newItem.setAmount(transferAction.getCount()); @@ -273,7 +284,6 @@ public abstract class InventoryTranslator { } } } else if (isCursor(transferAction.getDestination())) { //picking up into cursor - int sourceSlot = bedrockSlotToJava(transferAction.getSource()); GeyserItemStack sourceItem = plan.getItem(sourceSlot); int sourceAmount = sourceItem.getAmount(); if (cursor.isEmpty()) { //picking up into empty cursor @@ -303,7 +313,6 @@ public abstract class InventoryTranslator { if (!cursor.isEmpty()) { //TODO: handle slot transfer when cursor is already in use (temp slot) return rejectRequest(request); } - int sourceSlot = bedrockSlotToJava(transferAction.getSource()); int sourceAmount = plan.getItem(sourceSlot).getAmount(); if (transferAction.getCount() == sourceAmount) { //transfer all plan.add(Click.LEFT, sourceSlot); //pickup source @@ -339,7 +348,37 @@ public abstract class InventoryTranslator { if (!(checkNetId(session, inventory, swapAction.getSource()) && checkNetId(session, inventory, swapAction.getDestination()))) return rejectRequest(request); - if (isCursor(swapAction.getSource()) && isCursor(swapAction.getDestination())) { //??? + if (session.getGameMode().equals(GameMode.CREATIVE) && inventory instanceof PlayerInventory) { + int destSlot = bedrockSlotToJava(swapAction.getDestination()); + GeyserItemStack oldSourceItem; + GeyserItemStack oldDestinationItem = inventory.getItem(destSlot); + if (isCursor(swapAction.getSource())) { + oldSourceItem = session.getPlayerInventory().getCursor(); + session.getPlayerInventory().setCursor(oldDestinationItem); + } else { + int sourceSlot = bedrockSlotToJava(swapAction.getSource()); + oldSourceItem = inventory.getItem(sourceSlot); + ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( + sourceSlot, + oldDestinationItem.isEmpty() ? new ItemStack(0) : oldDestinationItem.getItemStack() // isEmpty check... just in case + ); + System.out.println(creativeActionPacket); + session.sendDownstreamPacket(creativeActionPacket); + inventory.setItem(sourceSlot, oldDestinationItem); + } + if (isCursor(swapAction.getDestination())) { + session.getPlayerInventory().setCursor(oldSourceItem); + } else { + ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( + destSlot, + oldSourceItem.isEmpty() ? new ItemStack(0) : oldSourceItem.getItemStack() + ); + System.out.println(creativeActionPacket); + session.sendDownstreamPacket(creativeActionPacket); + inventory.setItem(destSlot, oldSourceItem); + } + + } else if (isCursor(swapAction.getSource()) && isCursor(swapAction.getDestination())) { //??? return rejectRequest(request); } else if (isCursor(swapAction.getSource())) { //swap cursor int destSlot = bedrockSlotToJava(swapAction.getDestination()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java new file mode 100644 index 000000000..1ce6d078a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.connector.inventory.GeyserItemStack; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; +import org.geysermc.connector.network.translators.item.ItemRegistry; + +public class CartographyInventoryTranslator extends AbstractBlockInventoryTranslator { + public CartographyInventoryTranslator() { + super(3, "minecraft:cartography_table", ContainerType.CARTOGRAPHY, UIInventoryUpdater.INSTANCE); + } + + @Override + public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaSourceSlot, int javaDestinationSlot) { + if (javaDestinationSlot == 1) { + // Bedrock Edition can use a compass to create locator maps in the ADDITIONAL slot + GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot); + return ItemRegistry.getItem(itemStack.getItemStack()).getJavaIdentifier().equals("minecraft:compass"); + } else if (javaSourceSlot == 2) { + // Java doesn't allow an item to be renamed; this is why CARTOGRAPHY_ADDITIONAL could remain empty for Bedrock + return inventory.getItem(1).isEmpty(); + } + return false; + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.CARTOGRAPHY_INPUT) { + return 0; + } + if (slotInfoData.getContainer() == ContainerSlotType.CARTOGRAPHY_ADDITIONAL) { + return 1; + } + if (slotInfoData.getContainer() == ContainerSlotType.CARTOGRAPHY_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { + return 2; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + switch (slot) { + case 0: + return new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_INPUT, 12); + case 1: + return new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_ADDITIONAL, 13); + case 2: + return new BedrockContainerSlot(ContainerSlotType.CARTOGRAPHY_RESULT, 50); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int javaSlotToBedrock(int slot) { + switch (slot) { + case 0: + return 12; + case 1: + return 13; + case 2: + return 50; + } + return super.javaSlotToBedrock(slot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java index bacc83a02..47f213799 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java @@ -101,11 +101,11 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { } @Override - public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaDestinationSlot) { + public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaSourceSlot, int javaDestinationSlot) { if (javaDestinationSlot != 1) { return false; } - GeyserItemStack itemStack = session.getPlayerInventory().getCursor(); + GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot); if (itemStack.isEmpty()) { return false; } @@ -129,9 +129,9 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { } CraftResultsDeprecatedStackRequestActionData craftData = (CraftResultsDeprecatedStackRequestActionData) data; // Get the patterns compound tag - List newblockEntityTag = craftData.getResultItems()[0].getTag().getList("Patterns", NbtType.COMPOUND); + List newBlockEntityTag = craftData.getResultItems()[0].getTag().getList("Patterns", NbtType.COMPOUND); // Get the pattern that the Bedrock client requests - the last pattern in the Patterns list - NbtMap pattern = newblockEntityTag.get(newblockEntityTag.size() - 1); + NbtMap pattern = newBlockEntityTag.get(newBlockEntityTag.size() - 1); // Get the Java index of this pattern int index = PATTERN_TO_INDEX.getOrDefault(pattern.getString("Pattern"), -1); if (index == -1) { From c1f5380ed1777ebd717991832578a6847b320e10 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 26 Dec 2020 12:20:59 -0500 Subject: [PATCH 018/161] Add horse inventory support --- .../animal/horse/AbstractHorseEntity.java | 7 + .../animal/horse/ChestedHorseEntity.java | 3 + .../living/animal/horse/LlamaEntity.java | 4 +- .../connector/inventory/AnvilContainer.java | 6 +- .../connector/inventory/BeaconContainer.java | 5 +- .../connector/inventory/Container.java | 5 +- .../inventory/EnchantingContainer.java | 5 +- .../connector/inventory/FurnaceInventory.java | 5 +- .../connector/inventory/Inventory.java | 11 +- .../inventory/MerchantContainer.java | 5 +- .../connector/inventory/PlayerInventory.java | 2 +- .../network/session/GeyserSession.java | 4 + .../BedrockItemStackRequestTranslator.java | 2 +- .../player/BedrockInteractTranslator.java | 24 ++- .../inventory/InventoryTranslator.java | 8 +- .../translators/AnvilInventoryTranslator.java | 2 +- .../translators/BaseInventoryTranslator.java | 2 +- .../BeaconInventoryTranslator.java | 2 +- .../EnchantingInventoryTranslator.java | 2 +- .../translators/LoomInventoryTranslator.java | 1 + .../MerchantInventoryTranslator.java | 2 +- .../StonecutterInventoryTranslator.java | 1 + .../AbstractHorseInventoryTranslator.java | 66 +++++++++ .../ChestedHorseInventoryTranslator.java | 112 ++++++++++++++ .../horse/DonkeyInventoryTranslator.java | 32 ++++ .../horse/HorseInventoryTranslator.java | 52 +++++++ .../horse/LlamaInventoryTranslator.java | 32 ++++ .../updater/HorseInventoryUpdater.java | 69 +++++++++ .../inventory/updater/InventoryUpdater.java | 1 - .../window/JavaCloseWindowTranslator.java | 5 +- .../window/JavaOpenHorseWindowTranslator.java | 139 ++++++++++++++++++ .../java/window/JavaOpenWindowTranslator.java | 3 +- .../java/window/JavaSetSlotTranslator.java | 2 +- .../window/JavaWindowItemsTranslator.java | 3 +- .../window/JavaWindowPropertyTranslator.java | 2 +- .../connector/utils/InventoryUtils.java | 5 +- 36 files changed, 572 insertions(+), 59 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/DonkeyInventoryTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/HorseInventoryTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/LlamaInventoryTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java index cf9f84b42..8913da0da 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/AbstractHorseEntity.java @@ -30,6 +30,7 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import org.geysermc.connector.entity.living.animal.AnimalEntity; import org.geysermc.connector.entity.type.EntityType; @@ -40,6 +41,9 @@ public class AbstractHorseEntity extends AnimalEntity { public AbstractHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + + // Specifies the size of the entity's inventory. Required to place slots in the entity. + metadata.put(EntityData.CONTAINER_BASE_SIZE, 2); } @Override @@ -75,6 +79,9 @@ public class AbstractHorseEntity extends AnimalEntity { entityEventPacket.setData(ItemRegistry.WHEAT.getBedrockId() << 16); session.sendUpstreamPacket(entityEventPacket); } + + // Set container type if tamed + metadata.put(EntityData.CONTAINER_TYPE, ((xd & 0x02) == 0x02) ? (byte) ContainerType.HORSE.getId() : (byte) 0); } // Needed to control horses diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java index 7343f5e84..7b78aabb7 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/ChestedHorseEntity.java @@ -27,6 +27,7 @@ package org.geysermc.connector.entity.living.animal.horse; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -35,6 +36,8 @@ public class ChestedHorseEntity extends AbstractHorseEntity { public ChestedHorseEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + + metadata.put(EntityData.CONTAINER_BASE_SIZE, 16); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java index ddac4a63f..d33624e20 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/LlamaEntity.java @@ -38,6 +38,8 @@ public class LlamaEntity extends ChestedHorseEntity { public LlamaEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + + metadata.put(EntityData.CONTAINER_STRENGTH_MODIFIER, 3); // Presumably 3 slots for every 1 strength } @Override @@ -56,7 +58,7 @@ public class LlamaEntity extends ChestedHorseEntity { // The damage value is the dye color that Java sends us // Always going to be a carpet so we can hardcode 171 in BlockTranslator // The int then short conversion is required or we get a ClassCastException - equipmentPacket.setChestplate(ItemData.of(BlockTranslator.CARPET, (short)((int) entityMetadata.getValue()), 1)); + equipmentPacket.setChestplate(ItemData.of(BlockTranslator.CARPET, (short) ((int) entityMetadata.getValue()), 1)); } else { equipmentPacket.setChestplate(ItemData.AIR); } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java index d940ac75b..0b6482f29 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java @@ -25,10 +25,8 @@ package org.geysermc.connector.inventory; -import com.github.steveice10.mc.protocol.data.game.window.WindowType; - public class AnvilContainer extends Container { - public AnvilContainer(String title, int id, WindowType windowType, int size, PlayerInventory playerInventory) { - super(title, id, windowType, size, playerInventory); + public AnvilContainer(String title, int id, int size, PlayerInventory playerInventory) { + super(title, id, size, playerInventory); } } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java index 70ecd84f8..267565b8d 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java @@ -25,7 +25,6 @@ package org.geysermc.connector.inventory; -import com.github.steveice10.mc.protocol.data.game.window.WindowType; import lombok.Getter; import lombok.Setter; @@ -35,7 +34,7 @@ public class BeaconContainer extends Container { private int primaryId; private int secondaryId; - public BeaconContainer(String title, int id, WindowType windowType, int size, PlayerInventory playerInventory) { - super(title, id, windowType, size, playerInventory); + public BeaconContainer(String title, int id,int size, PlayerInventory playerInventory) { + super(title, id, size, playerInventory); } } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Container.java b/connector/src/main/java/org/geysermc/connector/inventory/Container.java index acf450e14..eb1d9c3de 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Container.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Container.java @@ -25,7 +25,6 @@ package org.geysermc.connector.inventory; -import com.github.steveice10.mc.protocol.data.game.window.WindowType; import lombok.Getter; import lombok.NonNull; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; @@ -38,8 +37,8 @@ public class Container extends Inventory { private final PlayerInventory playerInventory; private final int containerSize; - public Container(String title, int id, WindowType windowType, int size, PlayerInventory playerInventory) { - super(title, id, windowType, size); + public Container(String title, int id, int size, PlayerInventory playerInventory) { + super(title, id, size); this.playerInventory = playerInventory; this.containerSize = this.size + InventoryTranslator.PLAYER_INVENTORY_SIZE; } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java index c8b2bef13..ab0e544d2 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java @@ -25,7 +25,6 @@ package org.geysermc.connector.inventory; -import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData; import lombok.Getter; @@ -41,8 +40,8 @@ public class EnchantingContainer extends Container { @Getter private final GeyserEnchantOption[] geyserEnchantOptions; - public EnchantingContainer(String title, int id, WindowType windowType, int size, PlayerInventory playerInventory) { - super(title, id, windowType, size, playerInventory); + public EnchantingContainer(String title, int id, int size, PlayerInventory playerInventory) { + super(title, id, size, playerInventory); enchantOptions = new EnchantOptionData[3]; geyserEnchantOptions = new GeyserEnchantOption[3]; diff --git a/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java index 0af76244d..6574374be 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java @@ -26,7 +26,6 @@ package org.geysermc.connector.inventory; -import com.github.steveice10.mc.protocol.data.game.window.WindowType; import lombok.Getter; import lombok.Setter; @@ -36,7 +35,7 @@ public class FurnaceInventory extends Inventory { @Setter private int test; - public FurnaceInventory(String title, int id, WindowType windowType, int size) { - super(title, id, windowType, size); + public FurnaceInventory(String title, int id, int size) { + super(title, id, size); } } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java index fed94b0bf..14d80c385 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -25,7 +25,6 @@ package org.geysermc.connector.inventory; -import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.nukkitx.math.vector.Vector3i; import lombok.Getter; import lombok.NonNull; @@ -38,9 +37,6 @@ public class Inventory { @Getter protected int id; - @Getter - protected WindowType windowType; - @Getter protected final int size; @@ -64,14 +60,13 @@ public class Inventory { @Getter protected short transactionId = 0; - protected Inventory(int id, WindowType windowType, int size) { - this("Inventory", id, windowType, size); + protected Inventory(int id, int size) { + this("Inventory", id, size); } - protected Inventory(String title, int id, WindowType windowType, int size) { + protected Inventory(String title, int id, int size) { this.title = title; this.id = id; - this.windowType = windowType; this.size = size; this.items = new GeyserItemStack[size]; Arrays.fill(items, GeyserItemStack.EMPTY); diff --git a/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java index 03ae8ac3a..a33f81477 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java @@ -27,7 +27,6 @@ package org.geysermc.connector.inventory; import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; -import com.github.steveice10.mc.protocol.data.game.window.WindowType; import lombok.Getter; import lombok.Setter; import org.geysermc.connector.entity.Entity; @@ -38,7 +37,7 @@ public class MerchantContainer extends Container { private Entity villager; private VillagerTrade[] villagerTrades; - public MerchantContainer(String title, int id, WindowType windowType, int size, PlayerInventory playerInventory) { - super(title, id, windowType, size, playerInventory); + public MerchantContainer(String title, int id, int size, PlayerInventory playerInventory) { + super(title, id, size, playerInventory); } } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java index e6aeb5cab..52066a806 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java @@ -45,7 +45,7 @@ public class PlayerInventory extends Inventory { private GeyserItemStack cursor = GeyserItemStack.EMPTY; public PlayerInventory() { - super(0, null, 46); + super(0, 46); heldItemSlot = 0; } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 880e87128..df2936303 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -83,6 +83,7 @@ import org.geysermc.connector.network.translators.EntityIdentifierRegistry; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.collision.CollisionManager; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.skin.SkinManager; import org.geysermc.connector.utils.*; @@ -123,6 +124,9 @@ public class GeyserSession implements CommandSender { @Setter private Inventory openInventory; + @Setter + private InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR; + private final AtomicInteger itemNetId = new AtomicInteger(1); @Getter(AccessLevel.NONE) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java index 1ab68106e..65083ddef 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java @@ -43,7 +43,7 @@ public class BedrockItemStackRequestTranslator extends PacketTranslator translator.translateRequests(session, inventory, packet.getRequests())); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java index a2739cd8c..86f30c2fe 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java @@ -38,6 +38,7 @@ import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.InteractPacket; import lombok.Getter; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.living.animal.horse.AbstractHorseEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.network.session.GeyserSession; @@ -357,14 +358,23 @@ public class BedrockInteractTranslator extends PacketTranslator break; case OPEN_INVENTORY: if (session.getOpenInventory() == null) { - session.setOpenInventory(session.getPlayerInventory()); + Entity ridingEntity = session.getRidingVehicleEntity(); + if (ridingEntity instanceof AbstractHorseEntity) { + if (ridingEntity.getMetadata().getFlags().getFlag(EntityFlag.TAMED)) { + // We should request to open the horse inventory instead + ClientPlayerStatePacket openHorseWindowPacket = new ClientPlayerStatePacket((int)session.getPlayerEntity().getEntityId(), PlayerState.OPEN_HORSE_INVENTORY); + session.sendDownstreamPacket(openHorseWindowPacket); + } + } else { + session.setOpenInventory(session.getPlayerInventory()); - ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); - containerOpenPacket.setId((byte) 0); - containerOpenPacket.setType(ContainerType.INVENTORY); - containerOpenPacket.setUniqueEntityId(-1); - containerOpenPacket.setBlockPosition(entity.getPosition().toInt()); - session.sendUpstreamPacket(containerOpenPacket); + ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); + containerOpenPacket.setId((byte) 0); + containerOpenPacket.setType(ContainerType.INVENTORY); + containerOpenPacket.setUniqueEntityId(-1); + containerOpenPacket.setBlockPosition(entity.getPosition().toInt()); + session.sendUpstreamPacket(containerOpenPacket); + } } break; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 3e5bf0c67..dc09d922a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -60,10 +60,11 @@ import java.util.*; @AllArgsConstructor public abstract class InventoryTranslator { + public static final InventoryTranslator PLAYER_INVENTORY_TRANSLATOR = new PlayerInventoryTranslator(); public static final Map INVENTORY_TRANSLATORS = new HashMap() { { /* Player Inventory */ - put(null, new PlayerInventoryTranslator()); //player inventory + put(null, PLAYER_INVENTORY_TRANSLATOR); /* Chest UIs */ put(WindowType.GENERIC_9X1, new SingleChestInventoryTranslator(9)); @@ -95,9 +96,6 @@ public abstract class InventoryTranslator { /* Generics */ put(WindowType.GENERIC_3X3, new GenericBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER)); put(WindowType.HOPPER, new GenericBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER)); - - /* todo */ - // horse } }; @@ -193,7 +191,7 @@ public abstract class InventoryTranslator { int sourceSlot = bedrockSlotToJava(transferAction.getSource()); int destSlot = bedrockSlotToJava(transferAction.getDestination()); - if (shouldRejectItemPlace(session, inventory, isCursor(transferAction.getSource()) ? -1 :sourceSlot, destSlot)) { + if (shouldRejectItemPlace(session, inventory, isCursor(transferAction.getSource()) ? -1 : sourceSlot, destSlot)) { // This item would not be here in Java return rejectRequest(request, false); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java index 27de329c6..d4684057c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java @@ -82,6 +82,6 @@ public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { @Override public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { - return new AnvilContainer(name, windowId, windowType, this.size, playerInventory); + return new AnvilContainer(name, windowId, this.size, playerInventory); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BaseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BaseInventoryTranslator.java index 48d2ac109..32381d9ba 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BaseInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BaseInventoryTranslator.java @@ -96,6 +96,6 @@ public abstract class BaseInventoryTranslator extends InventoryTranslator { @Override public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { - return new Container(name, windowId, windowType, this.size, playerInventory); + return new Container(name, windowId, this.size, playerInventory); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java index 8ffc3e1a9..6407b9b39 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java @@ -129,6 +129,6 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator @Override public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { - return new BeaconContainer(name, windowId, windowType, this.size, playerInventory); + return new BeaconContainer(name, windowId, this.size, playerInventory); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java index c8ae8e2df..50e8ec6ac 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java @@ -170,7 +170,7 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla @Override public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { - return new EnchantingContainer(name, windowId, windowType, this.size, playerInventory); + return new EnchantingContainer(name, windowId, this.size, playerInventory); } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java index 47f213799..31388700b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java @@ -143,6 +143,7 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), index); System.out.println(packet); session.sendDownstreamPacket(packet); + GeyserItemStack inputCopy = inventory.getItem(0).copy(); inputCopy.setNetId(session.getItemNetId().incrementAndGet()); // Add the pattern manually, for better item synchronization diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java index 33a4e8240..c19a81be6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java @@ -149,6 +149,6 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator { @Override public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { - return new MerchantContainer(name, windowId, windowType, this.size, playerInventory); + return new MerchantContainer(name, windowId, this.size, playerInventory); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java index 7bac3c650..42e09400c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java @@ -57,6 +57,7 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl @Override public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { // TODO: Also surely to change in the future + // TODO: don't spam the ClickWindowButtonPacket? StackRequestActionData data = request.getActions()[1]; if (!(data instanceof CraftResultsDeprecatedStackRequestActionData)) { return rejectRequest(request); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java new file mode 100644 index 000000000..b33542d39 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators.horse; + +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.translators.BaseInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.updater.HorseInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; + +public abstract class AbstractHorseInventoryTranslator extends BaseInventoryTranslator { + private final InventoryUpdater updater; + + public AbstractHorseInventoryTranslator(int size) { + super(size); + this.updater = HorseInventoryUpdater.INSTANCE; + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + updater.updateInventory(this, session, inventory); + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + updater.updateSlot(this, session, inventory, slot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java new file mode 100644 index 000000000..65fc9dd7e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators.horse; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; + +import java.util.Arrays; + +public abstract class ChestedHorseInventoryTranslator extends AbstractHorseInventoryTranslator { + private final int chestSize; + private final int equipSlot; + + /** + * @param size the total Java size of the inventory + * @param equipSlot the Java equipment slot. For + */ + public ChestedHorseInventoryTranslator(int size, int equipSlot) { + super(size); + this.chestSize = size - 2; + this.equipSlot = equipSlot; + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.HORSE_EQUIP) { + return this.equipSlot; + } + if (slotInfoData.getContainer() == ContainerSlotType.CONTAINER) { + return slotInfoData.getSlot() + 1; + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == this.equipSlot) { + return new BedrockContainerSlot(ContainerSlotType.HORSE_EQUIP, 0); + } + if (slot <= this.size) { + return new BedrockContainerSlot(ContainerSlotType.CONTAINER, slot - 1); + } + return super.javaSlotToBedrockContainer(slot); + } + + @Override + public int javaSlotToBedrock(int slot) { + if (slot == 0 && this.equipSlot == 0) { + return 0; + } + if (slot <= this.size) { + return slot - 1; + } + return super.javaSlotToBedrock(slot); + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + ItemData[] bedrockItems = new ItemData[36]; + for (int i = 0; i < 36; i++) { + final int offset = i < 9 ? 27 : -9; + bedrockItems[i] = inventory.getItem(this.size + i + offset).getItemData(session); + } + InventoryContentPacket contentPacket = new InventoryContentPacket(); + contentPacket.setContainerId(ContainerId.INVENTORY); + contentPacket.setContents(Arrays.asList(bedrockItems)); + session.sendUpstreamPacket(contentPacket); + + ItemData[] horseItems = new ItemData[chestSize + 1]; + // Manually specify the first slot - Java always has two slots (armor and saddle) and one is invisible. + // Bedrock doesn't have this invisible slot. + horseItems[0] = inventory.getItem(this.equipSlot).getItemData(session); + for (int i = 1; i < horseItems.length; i++) { + horseItems[i] = inventory.getItem(i + 1).getItemData(session); + } + + InventoryContentPacket llamaPacket = new InventoryContentPacket(); + llamaPacket.setContainerId(inventory.getId()); + llamaPacket.setContents(Arrays.asList(horseItems)); + System.out.println(llamaPacket); + session.sendUpstreamPacket(llamaPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/DonkeyInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/DonkeyInventoryTranslator.java new file mode 100644 index 000000000..61a8d6926 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/DonkeyInventoryTranslator.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators.horse; + +public class DonkeyInventoryTranslator extends ChestedHorseInventoryTranslator { + public DonkeyInventoryTranslator(int size) { + super(size, 0); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/HorseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/HorseInventoryTranslator.java new file mode 100644 index 000000000..957933a02 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/HorseInventoryTranslator.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators.horse; + +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; + +public class HorseInventoryTranslator extends AbstractHorseInventoryTranslator { + public HorseInventoryTranslator(int size) { + super(size); + } + + @Override + public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { + if (slotInfoData.getContainer() == ContainerSlotType.HORSE_EQUIP) { + return slotInfoData.getSlot(); + } + return super.bedrockSlotToJava(slotInfoData); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { + if (slot == 0 || slot == 1) { + return new BedrockContainerSlot(ContainerSlotType.HORSE_EQUIP, slot); + } + return super.javaSlotToBedrockContainer(slot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/LlamaInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/LlamaInventoryTranslator.java new file mode 100644 index 000000000..523a1d3e7 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/LlamaInventoryTranslator.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators.horse; + +public class LlamaInventoryTranslator extends ChestedHorseInventoryTranslator { + public LlamaInventoryTranslator(int size) { + super(size, 1); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java new file mode 100644 index 000000000..2f88c49cd --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.updater; + +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; +import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; + +import java.util.Arrays; + +public class HorseInventoryUpdater extends InventoryUpdater { + public static final HorseInventoryUpdater INSTANCE = new HorseInventoryUpdater(); + + @Override + public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + super.updateInventory(translator, session, inventory); + + ItemData[] bedrockItems = new ItemData[translator.size]; + for (int i = 0; i < bedrockItems.length; i++) { + bedrockItems[translator.javaSlotToBedrock(i)] = inventory.getItem(i).getItemData(session); + } + + InventoryContentPacket contentPacket = new InventoryContentPacket(); + contentPacket.setContainerId(inventory.getId()); + contentPacket.setContents(Arrays.asList(bedrockItems)); + session.sendUpstreamPacket(contentPacket); + } + + @Override + public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) { + if (super.updateSlot(translator, session, inventory, javaSlot)) + return true; + + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(4); // Horse GUI? + slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); + slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); + session.sendUpstreamPacket(slotPacket); + System.out.println(slotPacket); + return true; + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java index e7d14f13a..d4026a23b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java @@ -32,7 +32,6 @@ import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import org.geysermc.connector.network.translators.item.ItemTranslator; import java.util.Arrays; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java index 68f1dda91..e84fd4380 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.java.window; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerCloseWindowPacket; -import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -37,8 +36,6 @@ public class JavaCloseWindowTranslator extends PacketTranslator { - InventoryUtils.closeInventory(session, packet.getWindowId()); - }); + session.addInventoryTask(() -> InventoryUtils.closeInventory(session, packet.getWindowId())); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java new file mode 100644 index 000000000..3d205d2a9 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.java.window; + +import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenHorseWindowPacket; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.UpdateEquipPacket; +import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.living.animal.horse.ChestedHorseEntity; +import org.geysermc.connector.entity.living.animal.horse.LlamaEntity; +import org.geysermc.connector.inventory.Container; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.inventory.translators.horse.DonkeyInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.translators.horse.HorseInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.translators.horse.LlamaInventoryTranslator; +import org.geysermc.connector.utils.InventoryUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Translator(packet = ServerOpenHorseWindowPacket.class) +public class JavaOpenHorseWindowTranslator extends PacketTranslator { + + private static final NbtMap ARMOR_SLOT; + private static final NbtMap CARPET_SLOT; + private static final NbtMap SADDLE_SLOT; + + static { + // Build the NBT mappings that Bedrock wants to lay out the GUI + String[] acceptedHorseArmorIdentifiers = new String[] {"minecraft:horsearmorleather", "minecraft:horsearmoriron", + "minecraft:horsearmorgold", "minecraft:horsearmordiamond"}; + NbtMapBuilder armorBuilder = NbtMap.builder(); + List acceptedArmors = new ArrayList<>(); + for (String identifier : acceptedHorseArmorIdentifiers) { + NbtMapBuilder acceptedItemBuilder = NbtMap.builder() + .putShort("Aux", Short.MAX_VALUE) + .putString("Name", identifier); + acceptedArmors.add(NbtMap.builder().putCompound("slotItem", acceptedItemBuilder.build()).build()); + } + armorBuilder.putList("acceptedItems", NbtType.COMPOUND, acceptedArmors); + NbtMapBuilder armorItem = NbtMap.builder() + .putShort("Aux", Short.MAX_VALUE) + .putString("Name", "minecraft:horsearmoriron"); + armorBuilder.putCompound("item", armorItem.build()); + armorBuilder.putInt("slotNumber", 1); + ARMOR_SLOT = armorBuilder.build(); + + NbtMapBuilder carpetBuilder = NbtMap.builder(); + NbtMapBuilder carpetItem = NbtMap.builder() + .putShort("Aux", Short.MAX_VALUE) + .putString("Name", "minecraft:carpet"); + List acceptedCarpet = Collections.singletonList(NbtMap.builder().putCompound("slotItem", carpetItem.build()).build()); + carpetBuilder.putList("acceptedItems", NbtType.COMPOUND, acceptedCarpet); + carpetBuilder.putCompound("item", carpetItem.build()); + carpetBuilder.putInt("slotNumber", 1); + CARPET_SLOT = carpetBuilder.build(); + + NbtMapBuilder saddleBuilder = NbtMap.builder(); + NbtMapBuilder acceptedSaddle = NbtMap.builder() + .putShort("Aux", Short.MAX_VALUE) + .putString("Name", "minecraft:saddle"); + List acceptedItem = Collections.singletonList(NbtMap.builder().putCompound("slotItem", acceptedSaddle.build()).build()); + saddleBuilder.putList("acceptedItems", NbtType.COMPOUND, acceptedItem); + saddleBuilder.putCompound("item", acceptedSaddle.build()); + saddleBuilder.putInt("slotNumber", 0); + SADDLE_SLOT = saddleBuilder.build(); + } + + @Override + public void translate(ServerOpenHorseWindowPacket packet, GeyserSession session) { + System.out.println(packet.toString()); + Entity entity = session.getEntityCache().getEntityByJavaId(packet.getEntityId()); + if (entity == null) { + return; + } + + UpdateEquipPacket updateEquipPacket = new UpdateEquipPacket(); + updateEquipPacket.setWindowId((short) packet.getWindowId()); + updateEquipPacket.setWindowType((short) ContainerType.HORSE.getId()); + updateEquipPacket.setUniqueEntityId(entity.getGeyserId()); + + NbtMapBuilder builder = NbtMap.builder(); + List slots = new ArrayList<>(); + + InventoryTranslator inventoryTranslator; + if (entity instanceof LlamaEntity) { + inventoryTranslator = new LlamaInventoryTranslator(packet.getNumberOfSlots()); + slots.add(CARPET_SLOT); + } else if (entity instanceof ChestedHorseEntity) { + inventoryTranslator = new DonkeyInventoryTranslator(packet.getNumberOfSlots()); + slots.add(SADDLE_SLOT); + } else { + inventoryTranslator = new HorseInventoryTranslator(packet.getNumberOfSlots()); + slots.add(SADDLE_SLOT); + slots.add(ARMOR_SLOT); + } + + // Build the NbtMap that sets the icons for Bedrock (e.g. sets the saddle outline on the saddle slot) + builder.putList("slots", NbtType.COMPOUND, slots); + + updateEquipPacket.setTag(builder.build()); + System.out.println(updateEquipPacket); + session.sendUpstreamPacket(updateEquipPacket); + + session.setInventoryTranslator(inventoryTranslator); + InventoryUtils.openInventory(session, new Container(entity.getMetadata().getString(EntityData.NAMETAG), packet.getWindowId(), packet.getNumberOfSlots(), session.getPlayerInventory())); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 89982dbb7..d32014280 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -63,12 +63,13 @@ public class JavaOpenWindowTranslator extends PacketTranslator if (inventory == null) return; - InventoryTranslator translator = InventoryTranslator.INVENTORY_TRANSLATORS.get(inventory.getWindowType()); + InventoryTranslator translator = session.getInventoryTranslator(); if (translator != null) { GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); GeyserItemStack oldItem = inventory.getItem(packet.getSlot()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java index 26aab47b3..7e88f067d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.java.window; import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerWindowItemsPacket; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; @@ -56,7 +55,7 @@ public class JavaWindowItemsTranslator extends PacketTranslator Date: Sat, 26 Dec 2020 13:24:50 -0500 Subject: [PATCH 019/161] Fix offset issue --- .../translators/horse/ChestedHorseInventoryTranslator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java index 65fc9dd7e..2450f18de 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java @@ -66,7 +66,7 @@ public abstract class ChestedHorseInventoryTranslator extends AbstractHorseInven if (slot == this.equipSlot) { return new BedrockContainerSlot(ContainerSlotType.HORSE_EQUIP, 0); } - if (slot <= this.size) { + if (slot <= this.size - 1) { // Accommodate for the lack of one slot (saddle or armor) return new BedrockContainerSlot(ContainerSlotType.CONTAINER, slot - 1); } return super.javaSlotToBedrockContainer(slot); @@ -77,7 +77,7 @@ public abstract class ChestedHorseInventoryTranslator extends AbstractHorseInven if (slot == 0 && this.equipSlot == 0) { return 0; } - if (slot <= this.size) { + if (slot <= this.size - 1) { return slot - 1; } return super.javaSlotToBedrock(slot); From 078af592491f2c0580030c63b8b11a1c73cfecf3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 26 Dec 2020 16:41:50 -0500 Subject: [PATCH 020/161] Streamline Item Net ID getting; cartography table finished --- .../connector/inventory/GeyserEnchantOption.java | 2 +- .../connector/network/session/GeyserSession.java | 13 +++++++++++-- .../bedrock/BedrockFilterTextTranslator.java | 2 +- .../translators/inventory/InventoryTranslator.java | 11 ++++++----- .../translators/CartographyInventoryTranslator.java | 9 ++++++--- .../translators/LoomInventoryTranslator.java | 5 ++--- .../translators/StonecutterInventoryTranslator.java | 2 +- .../horse/ChestedHorseInventoryTranslator.java | 13 +++++++------ .../network/translators/item/RecipeRegistry.java | 11 +++++++++++ .../java/JavaDeclareRecipesTranslator.java | 3 +++ .../java/window/JavaSetSlotTranslator.java | 4 ++-- .../java/window/JavaWindowItemsTranslator.java | 2 +- 12 files changed, 52 insertions(+), 25 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java index ea58372e2..ae4a9cf4e 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java @@ -73,6 +73,6 @@ public class GeyserEnchantOption { } return new EnchantOptionData(xpCost, javaIndex + 16, EMPTY, Collections.singletonList(new EnchantData(bedrockEnchantIndex, enchantLevel)), EMPTY, - javaEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(javaEnchantIndex), session.getItemNetId().incrementAndGet()); + javaEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(javaEnchantIndex), session.getNextItemNetId()); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index df2936303..bbbdf99c9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -127,6 +127,10 @@ public class GeyserSession implements CommandSender { @Setter private InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR; + /** + * Use {@link #getNextItemNetId()} instead for consistency + */ + @Getter(AccessLevel.NONE) private final AtomicInteger itemNetId = new AtomicInteger(1); @Getter(AccessLevel.NONE) @@ -727,12 +731,10 @@ public class GeyserSession implements CommandSender { startGamePacket.setLevelName(serverName); startGamePacket.setPremiumWorldTemplateId("00000000-0000-0000-0000-000000000000"); - // startGamePacket.setCurrentTick(0); startGamePacket.setEnchantmentSeed(0); startGamePacket.setMultiplayerCorrelationId(""); startGamePacket.setItemEntries(ItemRegistry.ITEMS); startGamePacket.setVanillaVersion("*"); - // startGamePacket.setMovementServerAuthoritative(true); startGamePacket.setInventoriesServerAuthoritative(true); startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); upstream.sendPacket(startGamePacket); @@ -773,6 +775,13 @@ public class GeyserSession implements CommandSender { } } + /** + * @return the next Bedrock item network ID to use for a new item + */ + public int getNextItemNetId() { + return itemNetId.getAndIncrement(); + } + public void addTeleport(TeleportCache teleportCache) { teleportMap.put(teleportCache.getTeleportConfirmId(), teleportCache); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java index cf06acf58..438be6b0b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java @@ -40,7 +40,7 @@ public class BedrockFilterTextTranslator extends PacketTranslator TIPPED_ARROW_RECIPES = new ObjectArrayList<>(); + /** + * Recipe data that, when sent to the client, enables cartography features. + * This does not have a Java equivalent. + */ + public static final List CARTOGRAPHY_RECIPE_DATA = new ObjectArrayList<>(); + /** * Recipe data that, when sent to the client, enables book cloning */ @@ -108,6 +114,11 @@ public class RecipeRegistry { MAP_EXTENDING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("d392b075-4ba1-40ae-8789-af868d56f6ce"), LAST_RECIPE_NET_ID++); MAP_CLONING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("85939755-ba10-4d9d-a4cc-efb7a8e943c4"), LAST_RECIPE_NET_ID++); BANNER_DUPLICATING_RECIPE_DATA = CraftingData.fromMulti(UUID.fromString("b5c5d105-75a2-4076-af2b-923ea2bf4bf0"), LAST_RECIPE_NET_ID++); + + CARTOGRAPHY_RECIPE_DATA.add(CraftingData.fromMulti(UUID.fromString("8b36268c-1829-483c-a0f1-993b7156a8f2"), LAST_RECIPE_NET_ID++)); // Map extending + CARTOGRAPHY_RECIPE_DATA.add(CraftingData.fromMulti(UUID.fromString("442d85ed-8272-4543-a6f1-418f90ded05d"), LAST_RECIPE_NET_ID++)); // Map cloning + CARTOGRAPHY_RECIPE_DATA.add(CraftingData.fromMulti(UUID.fromString("98c84b38-1085-46bd-b1ce-dd38c159e6cc"), LAST_RECIPE_NET_ID++)); // Map upgrading + CARTOGRAPHY_RECIPE_DATA.add(CraftingData.fromMulti(UUID.fromString("602234e4-cac1-4353-8bb7-b1ebff70024b"), LAST_RECIPE_NET_ID++)); // Map locking // https://github.com/pmmp/PocketMine-MP/blob/stable/src/pocketmine/inventory/MultiRecipe.php // Get all recipes that are not directly sent from a Java server diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index e74a82324..0c459133f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -149,6 +149,9 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator stonecutterRecipeMap = new Int2ObjectOpenHashMap<>(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java index 11dc7a48a..5d311fe3d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java @@ -47,7 +47,7 @@ public class JavaSetSlotTranslator extends PacketTranslator if (newItem.getItemData(session).equals(oldItem.getItemData(session))) { newItem.setNetId(oldItem.getNetId()); } else { - newItem.setNetId(session.getItemNetId().getAndIncrement()); + newItem.setNetId(session.getNextItemNetId()); } session.getPlayerInventory().setCursor(newItem); InventoryUtils.updateCursor(session); @@ -67,7 +67,7 @@ public class JavaSetSlotTranslator extends PacketTranslator newItem.setNetId(oldItem.getNetId()); System.out.println("OLD: " + newItem.getNetId()); } else { - newItem.setNetId(session.getItemNetId().getAndIncrement()); + newItem.setNetId(session.getNextItemNetId()); System.out.println("NEW: " + newItem.getNetId()); } inventory.setItem(packet.getSlot(), newItem); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java index 7e88f067d..b87f1de80 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java @@ -50,7 +50,7 @@ public class JavaWindowItemsTranslator extends PacketTranslator Date: Sat, 26 Dec 2020 19:16:35 -0500 Subject: [PATCH 021/161] Cleanup and delete FilterTextPacket - PRing to the main branch --- .../connector/inventory/BeaconContainer.java | 2 +- .../bedrock/BedrockFilterTextTranslator.java | 47 ------------------- .../item/translators/BannerTranslator.java | 2 - 3 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java index 267565b8d..768a49662 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/BeaconContainer.java @@ -34,7 +34,7 @@ public class BeaconContainer extends Container { private int primaryId; private int secondaryId; - public BeaconContainer(String title, int id,int size, PlayerInventory playerInventory) { + public BeaconContainer(String title, int id, int size, PlayerInventory playerInventory) { super(title, id, size, playerInventory); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java deleted file mode 100644 index 438be6b0b..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.bedrock; - -import com.nukkitx.protocol.bedrock.packet.FilterTextPacket; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.PacketTranslator; -import org.geysermc.connector.network.translators.Translator; - -/** - * Used to send strings to the client and filter out unwanted words. - * Java doesn't care, so we don't care, and we approve all strings. - */ -@Translator(packet = FilterTextPacket.class) -public class BedrockFilterTextTranslator extends PacketTranslator { - - @Override - public void translate(FilterTextPacket packet, GeyserSession session) { - // TODO: Bedrock doesn't send this. Why? - session.getConnector().getLogger().error(packet.toString()); - packet.setFromServer(true); - session.sendUpstreamPacket(packet); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java index 1870a1279..d5f5c6103 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/BannerTranslator.java @@ -32,7 +32,6 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.ItemRemapper; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; @@ -182,7 +181,6 @@ public class BannerTranslator extends ItemTranslator { @Override public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { - GeyserConnector.getInstance().getLogger().warning(itemEntry.toString()); if (itemData.getTag() == null) { return super.translateToJava(itemData, itemEntry); } From 60da3b94320d4e751837e487f40b21a2eabb7ef4 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Sat, 26 Dec 2020 18:44:48 -0900 Subject: [PATCH 022/161] Temp slot --- .../inventory/InventoryTranslator.java | 88 +++++++++++++++++-- 1 file changed, 79 insertions(+), 9 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index dc36ed177..3a201cdcd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -300,17 +300,33 @@ public abstract class InventoryTranslator { if (!InventoryUtils.canStack(cursor, plan.getItem(sourceSlot))) { //doesn't make sense, reject return rejectRequest(request); } - if (transferAction.getCount() != sourceAmount) { //TODO: handle partially picking up into non-empty cursor (temp slot) - return rejectRequest(request); + if (transferAction.getCount() != sourceAmount) { + int tempSlot = findTempSlot(inventory, cursor, false, sourceSlot); + if (tempSlot == -1) { + return rejectRequest(request); + } + plan.add(Click.LEFT, tempSlot); //place cursor into temp slot + plan.add(Click.LEFT, sourceSlot); //pickup source items into cursor + for (int i = 0; i < transferAction.getCount(); i++) { + plan.add(Click.RIGHT, tempSlot); //partially transfer source items into temp slot (original cursor) + } + plan.add(Click.LEFT, sourceSlot); //return remaining source items + plan.add(Click.LEFT, tempSlot); //retrieve original cursor items from temp slot + } else { + if (getSlotType(sourceSlot).equals(SlotType.NORMAL)) { + plan.add(Click.LEFT, sourceSlot); //release cursor onto source slot + } + plan.add(Click.LEFT, sourceSlot); //pickup combined cursor and source } - if (getSlotType(sourceSlot).equals(SlotType.NORMAL)) { - plan.add(Click.LEFT, sourceSlot); //release cursor onto source slot - } - plan.add(Click.LEFT, sourceSlot); //pickup combined cursor and source } } else { //transfer from one slot to another - if (!cursor.isEmpty()) { //TODO: handle slot transfer when cursor is already in use (temp slot) - return rejectRequest(request); + int tempSlot = -1; + if (!cursor.isEmpty()) { + tempSlot = findTempSlot(inventory, cursor, false, sourceSlot, destSlot); + if (tempSlot == -1) { + return rejectRequest(request); + } + plan.add(Click.LEFT, tempSlot); //place cursor into temp slot } int sourceAmount = plan.getItem(sourceSlot).getAmount(); if (transferAction.getCount() == sourceAmount) { //transfer all @@ -318,7 +334,7 @@ public abstract class InventoryTranslator { plan.add(Click.LEFT, destSlot); //let go of all items and done } else { //transfer some //try to transfer items with least clicks possible - int halfSource = sourceAmount / 2; //smaller half + int halfSource = sourceAmount - (sourceAmount / 2); //larger half int holding; if (transferAction.getCount() <= halfSource) { //faster to take only half plan.add(Click.RIGHT, sourceSlot); @@ -339,6 +355,9 @@ public abstract class InventoryTranslator { plan.add(Click.LEFT, sourceSlot); //return extra items to source slot } } + if (tempSlot != -1) { + plan.add(Click.LEFT, tempSlot); //retrieve original cursor + } } break; } @@ -724,11 +743,62 @@ public abstract class InventoryTranslator { public boolean checkNetId(GeyserSession session, Inventory inventory, StackRequestSlotInfoData slotInfoData) { if (slotInfoData.getStackNetworkId() < 0) return true; + if (slotInfoData.getContainer() == ContainerSlotType.CURSOR) //TODO: temporary + return true; GeyserItemStack currentItem = isCursor(slotInfoData) ? session.getPlayerInventory().getCursor() : inventory.getItem(bedrockSlotToJava(slotInfoData)); return currentItem.getNetId() == slotInfoData.getStackNetworkId(); } + /** + * Try to find a slot that can temporarily store the given item. + * Only looks in the main inventory and hotbar (excluding offhand). + * Only slots that are empty or contain a different type of item are valid. + * + * @return java id for the temporary slot, or -1 if no viable slot was found + */ + //TODO: compatibility for simulated inventory (ClickPlan) + private static int findTempSlot(Inventory inventory, GeyserItemStack item, boolean emptyOnly, int... slotBlacklist) { + int offset = inventory.getId() == 0 ? 1 : 0; //offhand is not a viable temp slot + HashSet itemBlacklist = new HashSet<>(slotBlacklist.length + 1); + itemBlacklist.add(item); + + IntSet potentialSlots = new IntOpenHashSet(36); + for (int i = inventory.getSize() - (36 + offset); i < inventory.getSize() - offset; i++) { + potentialSlots.add(i); + } + for (int i : slotBlacklist) { + potentialSlots.remove(i); + GeyserItemStack blacklistedItem = inventory.getItem(i); + if (!blacklistedItem.isEmpty()) { + itemBlacklist.add(blacklistedItem); + } + } + + for (int i : potentialSlots) { + GeyserItemStack testItem = inventory.getItem(i); + if ((emptyOnly && !testItem.isEmpty())) { + continue; + } + + boolean viable = true; + for (GeyserItemStack blacklistedItem : itemBlacklist) { + if (InventoryUtils.canStack(testItem, blacklistedItem)) { + viable = false; + break; + } + } + if (!viable) { + continue; + } + + System.out.println("TEMP SLOT CHOSEN: " + i + " => " + inventory.getItem(i)); + return i; + } + //could not find a viable temp slot + return -1; + } + public List makeContainerEntries(GeyserSession session, Inventory inventory, Set affectedSlots) { Map> containerMap = new HashMap<>(); for (int slot : affectedSlots) { From 956d264c3e68e8cb29281df29eb5bd59961615bb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 26 Dec 2020 22:55:14 -0500 Subject: [PATCH 023/161] Anvil renaming works; other things --- .../connector/inventory/AnvilContainer.java | 3 ++ .../inventory/CartographyContainer.java | 32 +++++++++++++++++++ .../bedrock/BedrockFilterTextTranslator.java | 14 ++++++++ .../inventory/InventoryTranslator.java | 20 +++++++++++- .../CartographyInventoryTranslator.java | 13 +++++--- 5 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/CartographyContainer.java diff --git a/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java index 0b6482f29..aba360a0d 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java @@ -25,6 +25,9 @@ package org.geysermc.connector.inventory; +/** + * Used to determine if rename packets should be sent. + */ public class AnvilContainer extends Container { public AnvilContainer(String title, int id, int size, PlayerInventory playerInventory) { super(title, id, size, playerInventory); diff --git a/connector/src/main/java/org/geysermc/connector/inventory/CartographyContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/CartographyContainer.java new file mode 100644 index 000000000..7e6d1cc79 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/CartographyContainer.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.inventory; + +public class CartographyContainer extends Container { + public CartographyContainer(String title, int id, int size, PlayerInventory playerInventory) { + super(title, id, size, playerInventory); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java index 8c9630764..a7400ff03 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockFilterTextTranslator.java @@ -25,7 +25,10 @@ package org.geysermc.connector.network.translators.bedrock; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket; import com.nukkitx.protocol.bedrock.packet.FilterTextPacket; +import org.geysermc.connector.inventory.AnvilContainer; +import org.geysermc.connector.inventory.CartographyContainer; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -39,7 +42,18 @@ public class BedrockFilterTextTranslator extends PacketTranslator Date: Sun, 27 Dec 2020 11:08:48 -0500 Subject: [PATCH 024/161] Add TODO --- .../network/translators/inventory/InventoryTranslator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index b960f09e2..95516dc26 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -462,6 +462,7 @@ public abstract class InventoryTranslator { } case DESTROY: { // Only called when a creative client wants to destroy an item... I think - Camotoy + //TODO there is a Count here we don't use DestroyStackRequestActionData destroyAction = (DestroyStackRequestActionData) action; if (!session.getGameMode().equals(GameMode.CREATIVE)) { // If this happens, let's throw an error and figure out why. From 2265de3ae90132148aecaa48a5547f4a7f08a2eb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 28 Dec 2020 00:29:27 -0500 Subject: [PATCH 025/161] lecterns --- .../platform/spigot/GeyserSpigotPlugin.java | 10 +- .../GeyserSpigot1_12NativeWorldManager.java | 4 +- .../manager/GeyserSpigot1_12WorldManager.java | 5 +- .../GeyserSpigotFallbackWorldManager.java | 5 +- .../GeyserSpigotLegacyNativeWorldManager.java | 2 +- .../GeyserSpigotNativeWorldManager.java | 5 +- .../manager/GeyserSpigotWorldManager.java | 70 +++++++- .../connector/inventory/LecternContainer.java | 44 +++++ .../network/session/GeyserSession.java | 10 ++ .../BedrockContainerCloseTranslator.java | 2 +- .../BedrockLecternUpdateTranslator.java | 95 +++++++++++ .../inventory/InventoryTranslator.java | 3 + .../LecternInventoryTranslator.java | 159 ++++++++++++++++++ .../java/world/JavaUnloadChunkTranslator.java | 11 +- .../translators/world/GeyserWorldManager.java | 26 +++ .../translators/world/WorldManager.java | 27 +++ .../world/block/BlockStateValues.java | 10 ++ .../geysermc/connector/utils/ChunkUtils.java | 33 +++- .../connector/utils/DimensionUtils.java | 1 + 19 files changed, 502 insertions(+), 20 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/LecternContainer.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 39d4f993b..c63c65d38 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -157,14 +157,14 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { if (isViaVersion && isViaVersionNeeded()) { if (isLegacy) { // Pre-1.13 - this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager(); + this.geyserWorldManager = new GeyserSpigot1_12NativeWorldManager(this); } else { // Post-1.13 this.geyserWorldManager = new GeyserSpigotLegacyNativeWorldManager(this, use3dBiomes); } } else { // No ViaVersion - this.geyserWorldManager = new GeyserSpigotNativeWorldManager(use3dBiomes); + this.geyserWorldManager = new GeyserSpigotNativeWorldManager(this, use3dBiomes); } geyserLogger.debug("Using NMS adapter: " + this.geyserWorldManager.getClass() + ", " + nmsVersion); } catch (Exception e) { @@ -180,13 +180,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { // No NMS adapter if (isLegacy && isViaVersion) { // Use ViaVersion for converting pre-1.13 block states - this.geyserWorldManager = new GeyserSpigot1_12WorldManager(); + this.geyserWorldManager = new GeyserSpigot1_12WorldManager(this); } else if (isLegacy) { // Not sure how this happens - without ViaVersion, we don't know any block states, so just assume everything is air - this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(); + this.geyserWorldManager = new GeyserSpigotFallbackWorldManager(this); } else { // Post-1.13 - this.geyserWorldManager = new GeyserSpigotWorldManager(use3dBiomes); + this.geyserWorldManager = new GeyserSpigotWorldManager(this, use3dBiomes); } geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass()); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java index f58b75cdd..67e114c47 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java @@ -27,6 +27,7 @@ package org.geysermc.platform.spigot.world.manager; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; import org.geysermc.adapters.spigot.SpigotAdapters; import org.geysermc.adapters.spigot.SpigotWorldAdapter; import org.geysermc.connector.network.session.GeyserSession; @@ -40,7 +41,8 @@ import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; public class GeyserSpigot1_12NativeWorldManager extends GeyserSpigot1_12WorldManager { private final SpigotWorldAdapter adapter; - public GeyserSpigot1_12NativeWorldManager() { + public GeyserSpigot1_12NativeWorldManager(Plugin plugin) { + super(plugin); this.adapter = SpigotAdapters.getWorldAdapter(); // Unlike post-1.13, we can't build up a cache of block states, because block entities need some special conversion } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java index b00ddafaa..881dece24 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java @@ -30,6 +30,7 @@ import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import us.myles.ViaVersion.api.Pair; @@ -61,8 +62,8 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { */ private final List> protocolList; - public GeyserSpigot1_12WorldManager() { - super(false); + public GeyserSpigot1_12WorldManager(Plugin plugin) { + super(plugin, false); this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData(); this.protocolList = ProtocolRegistry.getProtocolPath(CLIENT_PROTOCOL_VERSION, ProtocolVersion.v1_13.getVersion()); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java index 49c675a1d..dc0146baa 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java @@ -26,6 +26,7 @@ package org.geysermc.platform.spigot.world.manager; import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; +import org.bukkit.plugin.Plugin; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; @@ -35,9 +36,9 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; * If this occurs to you somehow, please let us know!! */ public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager { - public GeyserSpigotFallbackWorldManager() { + public GeyserSpigotFallbackWorldManager(Plugin plugin) { // Since this is pre-1.13 (and thus pre-1.15), there will never be 3D biomes. - super(false); + super(plugin, false); } @Override diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java index dec9b4141..376df3fd1 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotLegacyNativeWorldManager.java @@ -47,7 +47,7 @@ public class GeyserSpigotLegacyNativeWorldManager extends GeyserSpigotNativeWorl private final Int2IntMap oldToNewBlockId; public GeyserSpigotLegacyNativeWorldManager(GeyserSpigotPlugin plugin, boolean use3dBiomes) { - super(use3dBiomes); + super(plugin, use3dBiomes); IntList allBlockStates = adapter.getAllBlockStates(); oldToNewBlockId = new Int2IntOpenHashMap(allBlockStates.size()); ProtocolVersion serverVersion = plugin.getServerProtocolVersion(); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java index f703ecdb5..a23fc7b53 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java @@ -27,6 +27,7 @@ package org.geysermc.platform.spigot.world.manager; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; import org.geysermc.adapters.spigot.SpigotAdapters; import org.geysermc.adapters.spigot.SpigotWorldAdapter; import org.geysermc.connector.network.session.GeyserSession; @@ -35,8 +36,8 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager { protected final SpigotWorldAdapter adapter; - public GeyserSpigotNativeWorldManager(boolean use3dBiomes) { - super(use3dBiomes); + public GeyserSpigotNativeWorldManager(Plugin plugin, boolean use3dBiomes) { + super(plugin, use3dBiomes); adapter = SpigotAdapters.getWorldAdapter(); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java index cd1774baf..9b765fd6e 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -28,23 +28,35 @@ package org.geysermc.platform.spigot.world.manager; import com.fasterxml.jackson.databind.JsonNode; import com.github.steveice10.mc.protocol.MinecraftConstants; import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.block.Biome; import org.bukkit.block.Block; +import org.bukkit.block.Lectern; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.plugin.Plugin; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator; import org.geysermc.connector.network.translators.world.GeyserWorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.utils.BlockEntityUtils; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.GameRule; import org.geysermc.connector.utils.LanguageUtils; import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; /** * The base world manager to use when there is no supported NMS revision @@ -72,8 +84,11 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { */ private final Int2IntMap biomeToIdMap = new Int2IntOpenHashMap(Biome.values().length); - public GeyserSpigotWorldManager(boolean use3dBiomes) { + private final Plugin plugin; + + public GeyserSpigotWorldManager(Plugin plugin, boolean use3dBiomes) { this.use3dBiomes = use3dBiomes; + this.plugin = plugin; // Load the values into the biome-to-ID map InputStream biomeStream = FileUtils.getResource("biomes.json"); @@ -132,9 +147,6 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { @Override @SuppressWarnings("deprecation") public int[] getBiomeDataAt(GeyserSession session, int x, int z) { - if (session.getPlayerEntity() == null) { - return new int[1024]; - } int[] biomeData = new int[1024]; World world = Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld(); int chunkX = x << 4; @@ -167,6 +179,56 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { return biomeData; } + @Override + public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) { + // Run as a task to prevent async issues + Bukkit.getScheduler().runTask(this.plugin, () -> { + Player bukkitPlayer; + if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { + return; + } + Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z); + if (!(block.getState() instanceof Lectern)) { + session.getConnector().getLogger().error("Lectern expected at: " + Vector3i.from(x, y, z).toString() + " but was not! " + block.toString()); + return; + } + Lectern lectern = (Lectern) block.getState(); + ItemStack itemStack = lectern.getInventory().getItem(0); + if (itemStack == null || !(itemStack.getItemMeta() instanceof BookMeta)) { + if (!isChunkLoad) { + // We need to update the lectern since it's not going to be updated otherwise + BlockEntityUtils.updateBlockEntity(session, LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(), Vector3i.from(x, y, z)); + } + // We don't care; return + return; + } + BookMeta bookMeta = (BookMeta) itemStack.getItemMeta(); + NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, bookMeta.getPageCount()); + lecternTag.putInt("page", lectern.getPage() / 2); + NbtMapBuilder bookTag = NbtMap.builder() + .putByte("Count", (byte) itemStack.getAmount()) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:writable_book"); + List pages = new ArrayList<>(); + for (String page : bookMeta.getPages()) { + NbtMapBuilder pageBuilder = NbtMap.builder() + .putString("photoname", "") + .putString("text", page); + pages.add(pageBuilder.build()); + } + bookTag.putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, pages).build()); + lecternTag.putCompound("book", bookTag.build()); + NbtMap blockEntityTag = lecternTag.build(); + BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z)); + }); + return LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(); // Will be updated later + } + + @Override + public boolean shouldExpectLecternHandled() { + return true; + } + public Boolean getGameRuleBool(GeyserSession session, GameRule gameRule) { return Boolean.parseBoolean(Bukkit.getPlayer(session.getPlayerEntity().getUsername()).getWorld().getGameRuleValue(gameRule.getJavaID())); } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/LecternContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/LecternContainer.java new file mode 100644 index 000000000..0ce9217d7 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/LecternContainer.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.inventory; + +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; +import lombok.Getter; +import lombok.Setter; + +public class LecternContainer extends Container { + @Getter @Setter + private int currentBedrockPage = 0; + @Getter @Setter + private NbtMap blockEntityTag; + @Getter @Setter + private Vector3i position; + + public LecternContainer(String title, int id, int size, PlayerInventory playerInventory) { + super(title, id, size, playerInventory); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 436887d35..f00e9f0b1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -153,6 +153,16 @@ public class GeyserSession implements CommandSender { */ private final Object2LongMap itemFrameCache = new Object2LongOpenHashMap<>(); + /** + * Stores a list of all lectern locations and their block entity tags. + * See {@link org.geysermc.connector.network.translators.world.WorldManager#getLecternDataAt(GeyserSession, int, int, int, boolean)} + * for more information. + */ + private final List lecternCache = new ArrayList<>(); + + @Setter + private boolean droppingLecternBook; + @Setter private Vector2i lastChunkPosition = null; private int renderDistance; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java index 5571ff8b9..93837c204 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java @@ -50,7 +50,7 @@ public class BedrockContainerCloseTranslator extends PacketTranslator { + + @Override + public void translate(LecternUpdatePacket packet, GeyserSession session) { + session.getConnector().getLogger().error(packet.toString()); + if (packet.isDroppingBook()) { + // Bedrock drops the book outside of the GUI. Java drops it in the GUI + // So, we enter the GUI and then drop it! :) + session.setDroppingLecternBook(true); + + Vector3f diff = session.getPlayerEntity().getPosition().sub(packet.getBlockPosition().toFloat()); + System.out.println(diff); + // Emulate an interact packet + ClientPlayerPlaceBlockPacket blockPacket = new ClientPlayerPlaceBlockPacket( + new Position(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()), + BlockFace.values()[0], + Hand.MAIN_HAND, + packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ(), //TODO + false); + session.sendDownstreamPacket(blockPacket); + } else { + // Bedrock wants to either move a page or exit + LecternContainer lecternContainer = (LecternContainer) session.getOpenInventory(); + if (lecternContainer.getCurrentBedrockPage() == packet.getPage()) { + // The same page means Bedrock is closing the window + ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId()); + session.sendDownstreamPacket(closeWindowPacket); + InventoryUtils.closeInventory(session, lecternContainer.getId()); + } else { + // Each "page" Bedrock gives to us actually represents two pages (think opening a book and seeing two pages) + // Each "page" on Java is just one page (think a spiral notebook folded back to only show one page) + int newJavaPage = (packet.getPage() * 2); + int currentJavaPage = (lecternContainer.getCurrentBedrockPage() * 2); + // Send as many click button packets as we need to + // Java has the option to specify exact page numbers by adding 100 to the number, but buttonId variable + // is a byte and therefore this stops us at 128 + if (newJavaPage > currentJavaPage) { + for (int i = currentJavaPage; i < newJavaPage; i++) { + ClientClickWindowButtonPacket clickButtonPacket = new ClientClickWindowButtonPacket(session.getOpenInventory().getId(), 2); + System.out.println(clickButtonPacket); + session.sendDownstreamPacket(clickButtonPacket); + } + } else { + for (int i = currentJavaPage; i > newJavaPage; i--) { + ClientClickWindowButtonPacket clickButtonPacket = new ClientClickWindowButtonPacket(session.getOpenInventory().getId(), 1); + System.out.println(clickButtonPacket); + session.sendDownstreamPacket(clickButtonPacket); + } + } + } + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 95516dc26..396d1e090 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -97,6 +97,9 @@ public abstract class InventoryTranslator { /* Generics */ put(WindowType.GENERIC_3X3, new GenericBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER)); put(WindowType.HOPPER, new GenericBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER)); + + /* Lectern */ + put(WindowType.LECTERN, new LecternInventoryTranslator()); } }; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java new file mode 100644 index 000000000..28562bb9a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators; + +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import org.geysermc.connector.inventory.GeyserItemStack; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.LecternContainer; +import org.geysermc.connector.inventory.PlayerInventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; +import org.geysermc.connector.utils.BlockEntityUtils; +import org.geysermc.connector.utils.InventoryUtils; + +public class LecternInventoryTranslator extends BaseInventoryTranslator { + private final InventoryUpdater updater; + + public LecternInventoryTranslator() { + super(1); + this.updater = new LecternInventoryUpdater(); + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + if (key == 0) { // Lectern page update + LecternContainer lecternContainer = (LecternContainer) inventory; + lecternContainer.setCurrentBedrockPage(value / 2); + lecternContainer.setBlockEntityTag(lecternContainer.getBlockEntityTag().toBuilder().putInt("page", lecternContainer.getCurrentBedrockPage()).build()); + System.out.println(lecternContainer.getBlockEntityTag()); + BlockEntityUtils.updateBlockEntity(session, lecternContainer.getBlockEntityTag(), lecternContainer.getPosition()); + } + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + this.updater.updateSlot(this, session, inventory, slot); + if (slot == 0) { + LecternContainer lecternContainer = (LecternContainer) inventory; + if (session.isDroppingLecternBook()) { + // We have to enter the inventory GUI to eject the book + ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), 3); + session.sendDownstreamPacket(packet); + session.setDroppingLecternBook(false); + InventoryUtils.closeInventory(session, inventory.getId()); + } else if (lecternContainer.getBlockEntityTag() == null) { + // If the method returns true, this is already handled for us + GeyserItemStack geyserItemStack = inventory.getItem(0); + CompoundTag tag = geyserItemStack.getNbt(); + if (tag != null) { + // Position has to be the last interacted position... right? + Vector3i position = session.getLastInteractionPosition(); + // shouldRefresh means that we should boot out the + boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); + int pagesSize = ((ListTag) tag.get("pages")).size(); + ItemData itemData = geyserItemStack.getItemData(session); + NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), pagesSize); + lecternTag.putCompound("book", NbtMap.builder() + .putByte("Count", (byte) itemData.getCount()) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:written_book") + .putCompound("tag", itemData.getTag()) + .build()); + lecternTag.putInt("page", lecternContainer.getCurrentBedrockPage()); + NbtMap blockEntityTag = lecternTag.build(); + // Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild + // the block entity tag + lecternContainer.setBlockEntityTag(blockEntityTag); + System.out.println(blockEntityTag); + lecternContainer.setPosition(position); + if (shouldRefresh) { + // Update the lectern because it's not updated client-side + BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position); + session.getLecternCache().add(position); + // Close the window - we will reopen it once the client has this data synced + ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId()); + session.sendDownstreamPacket(closeWindowPacket); + InventoryUtils.closeInventory(session, inventory.getId()); + session.getConnector().getLogger().warning("Closing inventory"); + } + } + } + } + } + + @Override + public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { + return new LecternContainer(name, windowId, this.size, playerInventory); + } + + public static NbtMapBuilder getBaseLecternTag(int x, int y, int z, int totalPages) { + NbtMapBuilder builder = NbtMap.builder() + .putInt("x", x) + .putInt("y", y) + .putInt("z", z) + .putString("id", "Lectern"); + if (totalPages != 0) { + builder.putByte("hasBook", (byte) 1); + builder.putInt("totalPages", totalPages); + } else { + builder.putByte("hasBook", (byte) 0); + } + return builder; + } + + private static class LecternInventoryUpdater extends InventoryUpdater { + + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java index 1dba72868..a4a661cd9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java @@ -44,10 +44,19 @@ public class JavaUnloadChunkTranslator extends PacketTranslator iterator = session.getSkullCache().keySet().iterator(); while (iterator.hasNext()) { Vector3i position = iterator.next(); - if (Math.floor(position.getX() / 16) == packet.getX() && Math.floor(position.getZ() / 16) == packet.getZ()) { + if (Math.floor((double) position.getX() / 16) == packet.getX() && Math.floor((double) position.getZ() / 16) == packet.getZ()) { session.getSkullCache().get(position).despawnEntity(session); iterator.remove(); } } + + // Do the same thing with lecterns + iterator = session.getLecternCache().iterator(); + while (iterator.hasNext()) { + Vector3i position = iterator.next(); + if (Math.floor((double) position.getX() / 16) == packet.getX() && Math.floor((double) position.getZ() / 16) == packet.getZ()) { + iterator.remove(); + } + } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java index 2ab3c0108..6248a4944 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java @@ -30,10 +30,13 @@ import com.github.steveice10.mc.protocol.data.game.chunk.Column; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import com.github.steveice10.mc.protocol.packet.ingame.client.ClientChatPacket; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.ChunkCache; +import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator; import org.geysermc.connector.utils.GameRule; public class GeyserWorldManager extends WorldManager { @@ -88,6 +91,29 @@ public class GeyserWorldManager extends WorldManager { return new int[1024]; } + @Override + public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) { + // Without direct server access, we can't get lectern information on-the-fly. + // I should have set this up so it's only called when there is a book in the block state. - Camotoy + NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, 1); + lecternTag.putCompound("book", NbtMap.builder() + .putByte("Count", (byte) 1) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:written_book") + .putCompound("tag", NbtMap.builder() + .putString("photoname", "") + .putString("text", "") + .build()) + .build()); + lecternTag.putInt("page", -1); // I'm surprisingly glad this exists - it forces Bedrock to stop reading immediately + return lecternTag.build(); + } + + @Override + public boolean shouldExpectLecternHandled() { + return false; + } + @Override public void setGameRule(GeyserSession session, String name, Object value) { session.sendDownstreamPacket(new ClientChatPacket("/gamerule " + name + " " + value)); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java index aaafe2fe9..b17af92a9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java @@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.setting.Difficulty; import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.GameRule; @@ -106,6 +107,32 @@ public abstract class WorldManager { */ public abstract int[] getBiomeDataAt(GeyserSession session, int x, int z); + /** + * Sigh.
+ * + * So, on Java Edition, the lectern is an inventory. Java opens it and gets the contents of the book there. + * On Bedrock, the lectern contents are part of the block entity tag. Therefore, Bedrock expects to have the contents + * of the lectern ready and present in the world. If the contents are not there, it takes at least two clicks for the + * lectern to update the tag and then present itself.
+ * + * We solve this problem by querying all loaded lecterns, where possible, and sending their information in a block entity + * tag. + * + * @param session the session of the player + * @param x the x coordinate of the lectern + * @param y the y coordinate of the lectern + * @param z the z coordinate of the lectern + * @param isChunkLoad if this is called during a chunk load or not. Changes behavior in certain instances. + * @return the Bedrock lectern block entity tag. This may not be the exact block entity tag - for example, Spigot's + * block handled must be done on the server thread, so we send the tag manually there. + */ + public abstract NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad); + + /** + * @return whether we should expect lectern data to update, or if we have to fall back on a workaround. + */ + public abstract boolean shouldExpectLecternHandled(); + /** * Updates a gamerule value on the Java server * diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java index 2701f82fd..3c8dd2c7a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java @@ -42,6 +42,7 @@ public class BlockStateValues { private static final Int2ObjectMap DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); private static final Map FLOWER_POT_BLOCKS = new HashMap<>(); + private static final Int2BooleanMap LECTERN_BOOK_STATES = new Int2BooleanOpenHashMap(); private static final Int2IntMap NOTEBLOCK_PITCHES = new Int2IntOpenHashMap(); private static final Int2BooleanMap IS_STICKY_PISTON = new Int2BooleanOpenHashMap(); private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap(); @@ -88,6 +89,11 @@ public class BlockStateValues { return; } + if (entry.getKey().startsWith("minecraft:lectern")) { + LECTERN_BOOK_STATES.put(javaBlockState, entry.getKey().contains("has_book=true")); + return; + } + JsonNode notePitch = entry.getValue().get("note_pitch"); if (notePitch != null) { NOTEBLOCK_PITCHES.put(javaBlockState, entry.getValue().get("note_pitch").intValue()); @@ -197,6 +203,10 @@ public class BlockStateValues { return FLOWER_POT_BLOCKS; } + public static Int2BooleanMap getLecternBookStates() { + return LECTERN_BOOK_STATES; + } + /** * The note that noteblocks output when hit is part of the block state in Java but sent as a BlockEventPacket in Bedrock. * This gives an integer pitch that Bedrock can use. diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index 8950601a8..0175450da 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -53,6 +53,7 @@ import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator; import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.entity.BedrockOnlyBlockEntity; @@ -282,7 +283,6 @@ public class ChunkUtils { } String id = BlockEntityUtils.getBedrockBlockEntityId(tagName); - BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id); Position pos = new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue()); // Get Java blockstate ID from block entity position @@ -292,6 +292,14 @@ public class ChunkUtils { blockState = section.get(pos.getX() & 0xF, pos.getY() & 0xF, pos.getZ() & 0xF); } + if (tagName.equals("minecraft:lectern") && BlockStateValues.getLecternBookStates().get(blockState)) { + // If getLecternBookStates is false, let's just treat it like a normal block entity + bedrockBlockEntities[i] = session.getConnector().getWorldManager().getLecternDataAt(session, pos.getX(), pos.getY(), pos.getZ(), true); + i++; + continue; + } + + BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id); bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState); // Check for custom skulls @@ -388,6 +396,29 @@ public class ChunkUtils { } session.sendUpstreamPacket(waterPacket); + if (BlockStateValues.getLecternBookStates().containsKey(blockState)) { + boolean lecternCachedHasBook = session.getLecternCache().contains(position); + boolean newLecternHasBook = BlockStateValues.getLecternBookStates().get(blockState); + if (!session.getConnector().getWorldManager().shouldExpectLecternHandled() && lecternCachedHasBook != newLecternHasBook) { + // Refresh the block entirely - it either has a book or no longer has a book + session.getConnector().getLogger().warning("Refreshing lectern entirely"); + NbtMap newLecternTag; + if (newLecternHasBook) { + newLecternTag = session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false); + } else { + session.getLecternCache().remove(position); + newLecternTag = LecternInventoryTranslator.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 0).build(); + } + BlockEntityUtils.updateBlockEntity(session, newLecternTag, position); + } else { + // As of right now, no tag can be added asynchronously + session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false); + } + } else { + // Lectern has been destroyed, if it existed + session.getLecternCache().remove(position); + } + // Since Java stores bed colors/skull information as part of the namespaced ID and Bedrock stores it as a tag // This is the only place I could find that interacts with the Java block state and block updates // Iterates through all block entity translators and determines if the block state needs to be saved diff --git a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java index de9bcf884..e0118b431 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/DimensionUtils.java @@ -66,6 +66,7 @@ public class DimensionUtils { session.getEntityCache().removeAllEntities(); session.getItemFrameCache().clear(); + session.getLecternCache().clear(); session.getSkullCache().clear(); if (session.getPendingDimSwitches().getAndIncrement() > 0) { ChunkUtils.sendEmptyChunks(session, player.getPosition().toInt(), 3, true); From 3ba396e6259219e41092457d03d76a8773d67734 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 28 Dec 2020 00:47:10 -0500 Subject: [PATCH 026/161] Fix some temporary inventory blocks not disappearing --- .../translators/inventory/holder/BlockInventoryHolder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index 1b4dfb8db..7785c4a80 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -96,6 +96,7 @@ public class BlockInventoryHolder extends InventoryHolder { blockPacket.setDataLayer(0); blockPacket.setBlockPosition(holderPos); blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); + blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); } } From a5c020e7eeff3527f82700026278f8a20edafa01 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 28 Dec 2020 13:16:17 -0500 Subject: [PATCH 027/161] More comments --- .../inventory/translators/LecternInventoryTranslator.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java index 28562bb9a..0eabf951f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java @@ -72,7 +72,6 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { LecternContainer lecternContainer = (LecternContainer) inventory; lecternContainer.setCurrentBedrockPage(value / 2); lecternContainer.setBlockEntityTag(lecternContainer.getBlockEntityTag().toBuilder().putInt("page", lecternContainer.getCurrentBedrockPage()).build()); - System.out.println(lecternContainer.getBlockEntityTag()); BlockEntityUtils.updateBlockEntity(session, lecternContainer.getBlockEntityTag(), lecternContainer.getPosition()); } } @@ -100,7 +99,7 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { if (tag != null) { // Position has to be the last interacted position... right? Vector3i position = session.getLastInteractionPosition(); - // shouldRefresh means that we should boot out the + // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); int pagesSize = ((ListTag) tag.get("pages")).size(); ItemData itemData = geyserItemStack.getItemData(session); @@ -116,7 +115,6 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { // Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild // the block entity tag lecternContainer.setBlockEntityTag(blockEntityTag); - System.out.println(blockEntityTag); lecternContainer.setPosition(position); if (shouldRefresh) { // Update the lectern because it's not updated client-side @@ -148,6 +146,7 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { builder.putByte("hasBook", (byte) 1); builder.putInt("totalPages", totalPages); } else { + // Not usually needed, but helps with kicking out Bedrock players from reading the UI builder.putByte("hasBook", (byte) 0); } return builder; From 3c1a40c56af6a708b48c8077a90379176b6f876d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 29 Dec 2020 19:59:22 -0500 Subject: [PATCH 028/161] Better net ID handling --- .../org/geysermc/connector/entity/Entity.java | 6 +-- .../connector/inventory/Container.java | 7 ++-- .../connector/inventory/GeyserItemStack.java | 37 +++++++++++-------- .../connector/inventory/Inventory.java | 13 ++++++- ...BedrockInventoryTransactionTranslator.java | 2 +- .../entity/BedrockEntityEventTranslator.java | 2 +- .../player/BedrockInteractTranslator.java | 2 +- .../inventory/InventoryTranslator.java | 36 +++++++++--------- .../inventory/click/ClickPlan.java | 2 +- .../translators/LoomInventoryTranslator.java | 2 +- .../StonecutterInventoryTranslator.java | 6 +-- .../java/window/JavaSetSlotTranslator.java | 10 +---- .../window/JavaWindowItemsTranslator.java | 8 +--- .../connector/utils/InventoryUtils.java | 2 +- 14 files changed, 68 insertions(+), 67 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 68b2b2666..921d5e142 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -286,11 +286,11 @@ public class Entity { // Shield code if (session.getPlayerEntity().getEntityId() == entityId && metadata.getFlags().getFlag(EntityFlag.SNEAKING)) { PlayerInventory playerInv = session.getPlayerInventory(); - if ((playerInv.getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) || - (playerInv.getOffhand().getId() == ItemRegistry.SHIELD.getJavaId())) { + if ((playerInv.getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) || + (playerInv.getOffhand().getJavaId() == ItemRegistry.SHIELD.getJavaId())) { ClientPlayerUseItemPacket useItemPacket; metadata.getFlags().setFlag(EntityFlag.BLOCKING, true); - if (playerInv.getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) { + if (playerInv.getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) { useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); } // Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Container.java b/connector/src/main/java/org/geysermc/connector/inventory/Container.java index eb1d9c3de..9768520bd 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Container.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Container.java @@ -27,6 +27,7 @@ package org.geysermc.connector.inventory; import lombok.Getter; import lombok.NonNull; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; /** @@ -53,11 +54,11 @@ public class Container extends Inventory { } @Override - public void setItem(int slot, @NonNull GeyserItemStack item) { + public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { if (slot < this.size) { - super.setItem(slot, item); + super.setItem(slot, newItem, session); } else { - playerInventory.setItem(slot - this.size + InventoryTranslator.PLAYER_INVENTORY_OFFSET, item); + playerInventory.setItem(slot - this.size + InventoryTranslator.PLAYER_INVENTORY_OFFSET, newItem, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java index c935fcdb4..46cf8529a 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java @@ -39,32 +39,34 @@ import org.geysermc.connector.network.translators.item.ItemTranslator; public class GeyserItemStack { public static final GeyserItemStack EMPTY = new GeyserItemStack(0, 0, null); - private final int id; + private final int javaId; private int amount; private CompoundTag nbt; private int netId; + private boolean netIdWasUpdated; - public GeyserItemStack(int id) { - this(id, 1); + public GeyserItemStack(int javaId) { + this(javaId, 1); } - public GeyserItemStack(int id, int amount) { - this(id, amount, null); + public GeyserItemStack(int javaId, int amount) { + this(javaId, amount, null); } - public GeyserItemStack(int id, int amount, CompoundTag nbt) { - this(id, amount, nbt, 1); + public GeyserItemStack(int javaId, int amount, CompoundTag nbt) { + this(javaId, amount, nbt, 1); } - public GeyserItemStack(int id, int amount, CompoundTag nbt, int netId) { - this.id = id; + public GeyserItemStack(int javaId, int amount, CompoundTag nbt, int netId) { + this.javaId = javaId; this.amount = amount; this.nbt = nbt; this.netId = netId; + this.netIdWasUpdated = !this.isEmpty(); } - public int getId() { - return isEmpty() ? 0 : id; + public int getJavaId() { + return isEmpty() ? 0 : javaId; } public int getAmount() { @@ -75,6 +77,11 @@ public class GeyserItemStack { return isEmpty() ? null : nbt; } + public void setNetId(int netId) { + this.netId = netId; + this.netIdWasUpdated = true; + } + public int getNetId() { return isEmpty() ? 0 : netId; } @@ -96,7 +103,7 @@ public class GeyserItemStack { } public ItemStack getItemStack() { - return isEmpty() ? null : new ItemStack(id, amount, nbt); + return isEmpty() ? null : new ItemStack(javaId, amount, nbt); } public ItemData getItemData(GeyserSession session) { @@ -106,11 +113,11 @@ public class GeyserItemStack { } public ItemEntry getItemEntry() { - return ItemRegistry.ITEM_ENTRIES.get(getId()); + return ItemRegistry.ITEM_ENTRIES.get(getJavaId()); } public boolean isEmpty() { - return amount <= 0 || id == 0; + return amount <= 0 || javaId == 0; } public GeyserItemStack copy() { @@ -118,6 +125,6 @@ public class GeyserItemStack { } public GeyserItemStack copy(int newAmount) { - return isEmpty() ? EMPTY : new GeyserItemStack(id, newAmount, nbt == null ? null : nbt.clone(), netId); + return isEmpty() ? EMPTY : new GeyserItemStack(javaId, newAmount, nbt == null ? null : nbt.clone(), netId); } } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java index 14d80c385..53d0905ce 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -29,6 +29,7 @@ import com.nukkitx.math.vector.Vector3i; import lombok.Getter; import lombok.NonNull; import lombok.Setter; +import org.geysermc.connector.network.session.GeyserSession; import java.util.Arrays; @@ -76,8 +77,16 @@ public class Inventory { return items[slot]; } - public void setItem(int slot, @NonNull GeyserItemStack item) { - items[slot] = item; + public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { + GeyserItemStack oldItem = items[slot]; + if (!newItem.isEmpty()) { + if (newItem.getItemData(session).equals(oldItem.getItemData(session), false, false, false)) { + newItem.setNetId(oldItem.getNetId()); + } else { + newItem.setNetId(session.getNextItemNetId()); + } + } + items[slot] = newItem; } public short getNextTransactionId() { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 54221b4ad..9e71455c5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -186,7 +186,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator= 0 && packet.getData() < trades.length) { VillagerTrade trade = merchantInventory.getVillagerTrades()[packet.getData()]; - openInventory.setItem(2, GeyserItemStack.from(trade.getOutput())); + openInventory.setItem(2, GeyserItemStack.from(trade.getOutput()), session); villager.getMetadata().put(EntityData.TRADE_XP, trade.getXp() + villager.getMetadata().getInt(EntityData.TRADE_XP)); villager.updateBedrockMetadata(session); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java index 86f30c2fe..0c694fb9d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java @@ -100,7 +100,7 @@ public class BedrockInteractTranslator extends PacketTranslator switch (packet.getAction()) { case INTERACT: - if (session.getPlayerInventory().getItemInHand().getId() == ItemRegistry.SHIELD.getJavaId()) { + if (session.getPlayerInventory().getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) { break; } ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) entity.getEntityId(), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index ef052409f..7e626955a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -213,7 +213,7 @@ public abstract class InventoryTranslator { GeyserItemStack newItem = sourceItem.copy(); if (sourceIsCursor) { GeyserItemStack destItem = inventory.getItem(destSlot); - if (destItem.getId() == sourceItem.getId()) { + if (destItem.getJavaId() == sourceItem.getJavaId()) { // Combining items int itemsLeftOver = destItem.getAmount() + transferAction.getCount(); if (itemsLeftOver > MAX_ITEM_STACK_SIZE) { @@ -235,7 +235,7 @@ public abstract class InventoryTranslator { } } else { // Delete the source since we're moving it - inventory.setItem(sourceSlot, GeyserItemStack.EMPTY); + inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session); ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( sourceSlot, new ItemStack(0) @@ -252,13 +252,13 @@ public abstract class InventoryTranslator { if (sourceIsCursor) { session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY); } else { - inventory.setItem(sourceSlot, GeyserItemStack.EMPTY); + inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session); } } if (destIsCursor) { session.getPlayerInventory().setCursor(newItem); } else { - inventory.setItem(destSlot, newItem); + inventory.setItem(destSlot, newItem, session); } GeyserItemStack itemToUpdate = destIsCursor ? sourceItem : newItem; // The Java server doesn't care about what's in the mouse in creative mode, so we just need to track @@ -386,7 +386,7 @@ public abstract class InventoryTranslator { ); System.out.println(creativeActionPacket); session.sendDownstreamPacket(creativeActionPacket); - inventory.setItem(sourceSlot, oldDestinationItem); + inventory.setItem(sourceSlot, oldDestinationItem, session); } if (isCursor(swapAction.getDestination())) { session.getPlayerInventory().setCursor(oldSourceItem); @@ -397,7 +397,7 @@ public abstract class InventoryTranslator { ); System.out.println(creativeActionPacket); session.sendDownstreamPacket(creativeActionPacket); - inventory.setItem(destSlot, oldSourceItem); + inventory.setItem(destSlot, oldSourceItem, session); } } else if (isCursor(swapAction.getSource()) && isCursor(swapAction.getDestination())) { //??? @@ -499,7 +499,7 @@ public abstract class InventoryTranslator { ); session.sendDownstreamPacket(destroyItemPacket); System.out.println(destroyItemPacket); - inventory.setItem(javaSlot, GeyserItemStack.EMPTY); + inventory.setItem(javaSlot, GeyserItemStack.EMPTY, session); affectedSlots.add(javaSlot); } else { // Just sync up the item on our end, since the server doesn't care what's in our cursor @@ -522,7 +522,7 @@ public abstract class InventoryTranslator { GeyserItemStack item = inventory.getItem(sourceSlot); item.setAmount(item.getAmount() - consumeData.getCount()); if (item.isEmpty()) { - inventory.setItem(sourceSlot, GeyserItemStack.EMPTY); + inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session); } affectedSlots.add(sourceSlot); } @@ -718,16 +718,16 @@ public abstract class InventoryTranslator { session.getPlayerInventory().setCursor(GeyserItemStack.from(javaCreativeItem, session.getNextItemNetId())); return acceptRequest(request, Collections.singletonList( new ItemStackResponsePacket.ContainerEntry(ContainerSlotType.CURSOR, - Collections.singletonList(makeItemEntry(session, 0, session.getPlayerInventory().getCursor()))))); + Collections.singletonList(makeItemEntry(0, session.getPlayerInventory().getCursor()))))); } else { int javaSlot = bedrockSlotToJava(transferAction.getDestination()); GeyserItemStack existingItem = inventory.getItem(javaSlot); - if (existingItem.getId() == javaCreativeItem.getId()) { + if (existingItem.getJavaId() == javaCreativeItem.getId()) { // Adding more to an existing item existingItem.setAmount(existingItem.getAmount() + transferAction.getCount()); javaCreativeItem = existingItem.getItemStack(); } else { - inventory.setItem(javaSlot, GeyserItemStack.from(javaCreativeItem, session.getNextItemNetId())); + inventory.setItem(javaSlot, GeyserItemStack.from(javaCreativeItem, session.getNextItemNetId()), session); } ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( javaSlot, @@ -765,8 +765,8 @@ public abstract class InventoryTranslator { public boolean checkNetId(GeyserSession session, Inventory inventory, StackRequestSlotInfoData slotInfoData) { if (slotInfoData.getStackNetworkId() < 0) return true; - if (slotInfoData.getContainer() == ContainerSlotType.CURSOR) //TODO: temporary - return true; +// if (slotInfoData.getContainer() == ContainerSlotType.CURSOR) //TODO: temporary +// return true; GeyserItemStack currentItem = isCursor(slotInfoData) ? session.getPlayerInventory().getCursor() : inventory.getItem(bedrockSlotToJava(slotInfoData)); return currentItem.getNetId() == slotInfoData.getStackNetworkId(); @@ -826,7 +826,7 @@ public abstract class InventoryTranslator { for (int slot : affectedSlots) { BedrockContainerSlot bedrockSlot = javaSlotToBedrockContainer(slot); List list = containerMap.computeIfAbsent(bedrockSlot.getContainer(), k -> new ArrayList<>()); - list.add(makeItemEntry(session, bedrockSlot.getSlot(), inventory.getItem(slot))); + list.add(makeItemEntry(bedrockSlot.getSlot(), inventory.getItem(slot))); } List containerEntries = new ArrayList<>(); @@ -834,18 +834,16 @@ public abstract class InventoryTranslator { containerEntries.add(new ItemStackResponsePacket.ContainerEntry(entry.getKey(), entry.getValue())); } - ItemStackResponsePacket.ItemEntry cursorEntry = makeItemEntry(session, 0, session.getPlayerInventory().getCursor()); + ItemStackResponsePacket.ItemEntry cursorEntry = makeItemEntry(0, session.getPlayerInventory().getCursor()); containerEntries.add(new ItemStackResponsePacket.ContainerEntry(ContainerSlotType.CURSOR, Collections.singletonList(cursorEntry))); return containerEntries; } - public static ItemStackResponsePacket.ItemEntry makeItemEntry(GeyserSession session, int bedrockSlot, GeyserItemStack itemStack) { + public static ItemStackResponsePacket.ItemEntry makeItemEntry(int bedrockSlot, GeyserItemStack itemStack) { ItemStackResponsePacket.ItemEntry itemEntry; if (!itemStack.isEmpty()) { - int newNetId = session.getNextItemNetId(); - itemStack.setNetId(newNetId); - itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) itemStack.getAmount(), newNetId, ""); + itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) itemStack.getAmount(), itemStack.getNetId(), ""); } else { itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) 0, 0, ""); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java index 4e5c28552..5bfe197e5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java @@ -127,7 +127,7 @@ public class ClickPlan { if (simulating) { simulatedItems.put(slot, item); } else { - inventory.setItem(slot, item); + inventory.setItem(slot, item, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java index 59dd7d742..7fbca6e92 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java @@ -166,7 +166,7 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { inputCopy.getNbt().put(blockEntityTag); } // Set the new item as the output - inventory.setItem(3, inputCopy); + inventory.setItem(3, inputCopy, session); return translateRequest(session, inventory, request); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java index 16aefc744..4ffe87eb0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java @@ -64,7 +64,7 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl } CraftResultsDeprecatedStackRequestActionData craftData = (CraftResultsDeprecatedStackRequestActionData) data; // Get the ID of the item we are cutting - int id = inventory.getItem(0).getId(); + int id = inventory.getItem(0).getJavaId(); // Look up all possible options of cutting from this ID IntList results = session.getStonecutterRecipes().get(id); if (results == null) { @@ -77,9 +77,9 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), results.indexOf(javaOutput.getId())); System.out.println(packet.toString()); session.sendDownstreamPacket(packet); - if (inventory.getItem(1).getId() != javaOutput.getId()) { + if (inventory.getItem(1).getJavaId() != javaOutput.getId()) { // We don't know there is an output here, so we tell ourselves that there is - inventory.setItem(1, GeyserItemStack.from(javaOutput, session.getNextItemNetId())); + inventory.setItem(1, GeyserItemStack.from(javaOutput, session.getNextItemNetId()), session); } return translateRequest(session, inventory, request); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java index 5d311fe3d..94c572712 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java @@ -62,15 +62,7 @@ public class JavaSetSlotTranslator extends PacketTranslator InventoryTranslator translator = session.getInventoryTranslator(); if (translator != null) { GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); - GeyserItemStack oldItem = inventory.getItem(packet.getSlot()); - if (newItem.getItemData(session).equals(oldItem.getItemData(session), false, false, false)) { - newItem.setNetId(oldItem.getNetId()); - System.out.println("OLD: " + newItem.getNetId()); - } else { - newItem.setNetId(session.getNextItemNetId()); - System.out.println("NEW: " + newItem.getNetId()); - } - inventory.setItem(packet.getSlot(), newItem); + inventory.setItem(packet.getSlot(), newItem, session); translator.updateSlot(session, inventory, packet.getSlot()); } }); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java index b87f1de80..c8afd1c53 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowItemsTranslator.java @@ -46,13 +46,7 @@ public class JavaWindowItemsTranslator extends PacketTranslator Date: Tue, 29 Dec 2020 20:31:48 -0500 Subject: [PATCH 029/161] Optimize stonecutter button code --- .../connector/inventory/FurnaceInventory.java | 41 -------------- .../inventory/StonecutterContainer.java | 53 +++++++++++++++++++ .../StonecutterInventoryTranslator.java | 29 +++++++--- 3 files changed, 74 insertions(+), 49 deletions(-) delete mode 100644 connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/StonecutterContainer.java diff --git a/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java deleted file mode 100644 index 6574374be..000000000 --- a/connector/src/main/java/org/geysermc/connector/inventory/FurnaceInventory.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - * - */ - -package org.geysermc.connector.inventory; - -import lombok.Getter; -import lombok.Setter; - -//TODO: Figure out what this is and if we should remove it -@Getter -public class FurnaceInventory extends Inventory { - @Setter - private int test; - - public FurnaceInventory(String title, int id, int size) { - super(title, id, size); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/inventory/StonecutterContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/StonecutterContainer.java new file mode 100644 index 000000000..46db48b16 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/StonecutterContainer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.inventory; + +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import org.geysermc.connector.network.session.GeyserSession; + +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, PlayerInventory playerInventory) { + super(title, id, size, playerInventory); + } + + @Override + public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { + if (slot == 0 && newItem.getJavaId() != items[slot].getJavaId()) { + // The pressed stonecutter button output resets whenever the input item changes + this.stonecutterButton = -1; + } + super.setItem(slot, newItem, session); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java index 4ffe87eb0..419e80603 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.connector.network.translators.inventory.translators; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; @@ -38,6 +39,8 @@ import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import it.unimi.dsi.fastutil.ints.IntList; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.PlayerInventory; +import org.geysermc.connector.inventory.StonecutterContainer; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; import org.geysermc.connector.network.translators.inventory.SlotType; @@ -57,12 +60,12 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl @Override public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { // TODO: Also surely to change in the future - // TODO: don't spam the ClickWindowButtonPacket? StackRequestActionData data = request.getActions()[1]; if (!(data instanceof CraftResultsDeprecatedStackRequestActionData)) { return rejectRequest(request); } CraftResultsDeprecatedStackRequestActionData craftData = (CraftResultsDeprecatedStackRequestActionData) data; + StonecutterContainer container = (StonecutterContainer) inventory; // Get the ID of the item we are cutting int id = inventory.getItem(0).getJavaId(); // Look up all possible options of cutting from this ID @@ -73,13 +76,18 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl System.out.println(id + " " + results); ItemStack javaOutput = ItemTranslator.translateToJava(craftData.getResultItems()[0]); System.out.println(javaOutput); - // Getting the index of the item in the Java stonecutter list - ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), results.indexOf(javaOutput.getId())); - System.out.println(packet.toString()); - session.sendDownstreamPacket(packet); - if (inventory.getItem(1).getJavaId() != javaOutput.getId()) { - // We don't know there is an output here, so we tell ourselves that there is - inventory.setItem(1, GeyserItemStack.from(javaOutput, session.getNextItemNetId()), session); + int button = results.indexOf(javaOutput.getId()); + // If we've already pressed the button with this item, no need to press it again! + if (container.getStonecutterButton() != button) { + // Getting the index of the item in the Java stonecutter list + ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), button); + System.out.println(packet.toString()); + session.sendDownstreamPacket(packet); + container.setStonecutterButton(button); + if (inventory.getItem(1).getJavaId() != javaOutput.getId()) { + // We don't know there is an output here, so we tell ourselves that there is + inventory.setItem(1, GeyserItemStack.from(javaOutput, session.getNextItemNetId()), session); + } } return translateRequest(session, inventory, request); } @@ -124,4 +132,9 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl } return super.getSlotType(javaSlot); } + + @Override + public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { + return new StonecutterContainer(name, windowId, this.size, playerInventory); + } } From b7b3278d8bf3d19a6dd35b64a3fceb56f0c53fd0 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Thu, 31 Dec 2020 17:39:54 -0900 Subject: [PATCH 030/161] item painting. cursor net id --- .../connector/inventory/GeyserItemStack.java | 3 --- .../connector/inventory/Inventory.java | 6 +++++- .../connector/inventory/PlayerInventory.java | 7 ++++++- .../inventory/InventoryTranslator.java | 18 +++++++++--------- .../translators/inventory/click/ClickPlan.java | 18 +++++++++++++++--- .../JavaConfirmTransactionTranslator.java | 1 + .../java/window/JavaSetSlotTranslator.java | 8 +------- .../connector/utils/InventoryUtils.java | 2 +- 8 files changed, 38 insertions(+), 25 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java index 46cf8529a..c84439657 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java @@ -43,7 +43,6 @@ public class GeyserItemStack { private int amount; private CompoundTag nbt; private int netId; - private boolean netIdWasUpdated; public GeyserItemStack(int javaId) { this(javaId, 1); @@ -62,7 +61,6 @@ public class GeyserItemStack { this.amount = amount; this.nbt = nbt; this.netId = netId; - this.netIdWasUpdated = !this.isEmpty(); } public int getJavaId() { @@ -79,7 +77,6 @@ public class GeyserItemStack { public void setNetId(int netId) { this.netId = netId; - this.netIdWasUpdated = true; } public int getNetId() { diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java index 53d0905ce..1088f6f94 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -79,6 +79,11 @@ public class Inventory { public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { GeyserItemStack oldItem = items[slot]; + updateItemNetId(oldItem, newItem, session); + items[slot] = newItem; + } + + protected static void updateItemNetId(GeyserItemStack oldItem, GeyserItemStack newItem, GeyserSession session) { if (!newItem.isEmpty()) { if (newItem.getItemData(session).equals(oldItem.getItemData(session), false, false, false)) { newItem.setNetId(oldItem.getNetId()); @@ -86,7 +91,6 @@ public class Inventory { newItem.setNetId(session.getNextItemNetId()); } } - items[slot] = newItem; } public short getNextTransactionId() { diff --git a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java index 52066a806..b9f00b5f7 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java @@ -28,6 +28,7 @@ package org.geysermc.connector.inventory; import lombok.Getter; import lombok.NonNull; import lombok.Setter; +import org.geysermc.connector.network.session.GeyserSession; public class PlayerInventory extends Inventory { @@ -40,7 +41,6 @@ public class PlayerInventory extends Inventory { private int heldItemSlot; @Getter - @Setter @NonNull private GeyserItemStack cursor = GeyserItemStack.EMPTY; @@ -49,6 +49,11 @@ public class PlayerInventory extends Inventory { heldItemSlot = 0; } + public void setCursor(@NonNull GeyserItemStack newCursor, GeyserSession session) { + updateItemNetId(cursor, newCursor, session); + cursor = newCursor; + } + public GeyserItemStack getItemInHand() { return items[36 + heldItemSlot]; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 7e626955a..11ce68e49 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -223,7 +223,7 @@ public abstract class InventoryTranslator { } else { // Cursor will be emptied destItem.setAmount(itemsLeftOver); - session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY); + session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session); } ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( destSlot, @@ -250,13 +250,13 @@ public abstract class InventoryTranslator { if (sourceItem.isEmpty()) { // Item is basically deleted if (sourceIsCursor) { - session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY); + session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session); } else { inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session); } } if (destIsCursor) { - session.getPlayerInventory().setCursor(newItem); + session.getPlayerInventory().setCursor(newItem, session); } else { inventory.setItem(destSlot, newItem, session); } @@ -340,7 +340,7 @@ public abstract class InventoryTranslator { //try to transfer items with least clicks possible int halfSource = sourceAmount - (sourceAmount / 2); //larger half int holding; - if (transferAction.getCount() <= halfSource) { //faster to take only half + if (plan.getCursor().isEmpty() && transferAction.getCount() <= halfSource) { //faster to take only half. CURSOR MUST BE EMPTY plan.add(Click.RIGHT, sourceSlot); holding = halfSource; } else { //need all @@ -376,7 +376,7 @@ public abstract class InventoryTranslator { GeyserItemStack oldDestinationItem = inventory.getItem(destSlot); if (isCursor(swapAction.getSource())) { oldSourceItem = session.getPlayerInventory().getCursor(); - session.getPlayerInventory().setCursor(oldDestinationItem); + session.getPlayerInventory().setCursor(oldDestinationItem, session); } else { int sourceSlot = bedrockSlotToJava(swapAction.getSource()); oldSourceItem = inventory.getItem(sourceSlot); @@ -389,7 +389,7 @@ public abstract class InventoryTranslator { inventory.setItem(sourceSlot, oldDestinationItem, session); } if (isCursor(swapAction.getDestination())) { - session.getPlayerInventory().setCursor(oldSourceItem); + session.getPlayerInventory().setCursor(oldSourceItem, session); } else { ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( destSlot, @@ -445,7 +445,7 @@ public abstract class InventoryTranslator { cursorItem.setAmount(cursorItem.getAmount() - dropAction.getCount()); if (cursorItem.isEmpty()) { // Cursor item no longer exists - session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY); + session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session); } droppingItem.setAmount(dropAction.getCount()); ClientCreativeInventoryActionPacket packet = new ClientCreativeInventoryActionPacket( @@ -503,7 +503,7 @@ public abstract class InventoryTranslator { affectedSlots.add(javaSlot); } else { // Just sync up the item on our end, since the server doesn't care what's in our cursor - session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY); + session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session); } break; } @@ -715,7 +715,7 @@ public abstract class InventoryTranslator { ItemStack javaCreativeItem = ItemTranslator.translateToJava(creativeItem); if (isCursor(transferAction.getDestination())) { - session.getPlayerInventory().setCursor(GeyserItemStack.from(javaCreativeItem, session.getNextItemNetId())); + session.getPlayerInventory().setCursor(GeyserItemStack.from(javaCreativeItem, session.getNextItemNetId()), session); return acceptRequest(request, Collections.singletonList( new ItemStackResponsePacket.ContainerEntry(ContainerSlotType.CURSOR, Collections.singletonList(makeItemEntry(0, session.getPlayerInventory().getCursor()))))); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java index 5bfe197e5..64174c496 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java @@ -63,6 +63,11 @@ public class ClickPlan { this.simulating = true; } + private void resetSimulation() { + this.simulatedItems.clear(); + this.simulatedCursor = session.getPlayerInventory().getCursor().copy(); + } + public void add(Click click, int slot) { if (!simulating) throw new UnsupportedOperationException("ClickPlan already executed"); @@ -77,7 +82,8 @@ public class ClickPlan { } public void execute(boolean refresh) { - simulating = false; + //update geyser inventory after simulation to avoid net id desync + resetSimulation(); ListIterator planIter = plan.listIterator(); while (planIter.hasNext()) { ClickAction action = planIter.next(); @@ -92,7 +98,7 @@ public class ClickPlan { } else if (action.click.windowAction == WindowAction.DROP_ITEM || action.slot == Click.OUTSIDE_SLOT) { clickedItemStack = null; } else { - clickedItemStack = inventory.getItem(action.slot).getItemStack(); + clickedItemStack = getItem(action.slot).getItemStack(); } short actionId = inventory.getNextTransactionId(); @@ -113,6 +119,12 @@ public class ClickPlan { } System.out.println(clickPacket); } + + session.getPlayerInventory().setCursor(simulatedCursor, session); + for (Int2ObjectMap.Entry simulatedSlot : simulatedItems.int2ObjectEntrySet()) { + inventory.setItem(simulatedSlot.getIntKey(), simulatedSlot.getValue(), session); + } + simulating = false; } public GeyserItemStack getItem(int slot) { @@ -135,7 +147,7 @@ public class ClickPlan { if (simulating) { simulatedCursor = item; } else { - session.getPlayerInventory().setCursor(item); + session.getPlayerInventory().setCursor(item, session); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java index 6d6c1686c..69e9b4ad0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java @@ -41,6 +41,7 @@ public class JavaConfirmTransactionTranslator extends PacketTranslator session.addInventoryTask(() -> { if (packet.getWindowId() == 255) { //cursor GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); - GeyserItemStack oldItem = session.getPlayerInventory().getCursor(); - if (newItem.getItemData(session).equals(oldItem.getItemData(session))) { - newItem.setNetId(oldItem.getNetId()); - } else { - newItem.setNetId(session.getNextItemNetId()); - } - session.getPlayerInventory().setCursor(newItem); + session.getPlayerInventory().setCursor(newItem, session); InventoryUtils.updateCursor(session); return; } diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java index a15b5a1c7..854654fb7 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -79,7 +79,7 @@ public class InventoryUtils { } public static void closeInventory(GeyserSession session, int windowId) { - session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY); + session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session); updateCursor(session); Inventory inventory = getInventory(session, windowId); From 2a5c134ea7cc2d86bede701a7f53fa46dbd07029 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 1 Jan 2021 14:22:26 -0500 Subject: [PATCH 031/161] Update server inventory copyrights to 2021 --- .../connector/inventory/AnvilContainer.java | 2 +- .../inventory/CartographyContainer.java | 2 +- .../connector/inventory/Container.java | 2 +- .../inventory/EnchantingContainer.java | 2 +- .../inventory/GeyserEnchantOption.java | 2 +- .../connector/inventory/GeyserItemStack.java | 37 +++++++++---------- .../connector/inventory/LecternContainer.java | 2 +- .../inventory/MerchantContainer.java | 37 +++++++++---------- .../BedrockItemStackRequestTranslator.java | 37 +++++++++---------- .../BedrockLecternUpdateTranslator.java | 2 +- .../inventory/BedrockContainerSlot.java | 37 +++++++++---------- .../translators/inventory/click/Click.java | 2 +- .../inventory/click/ClickPlan.java | 2 +- .../translators/AnvilInventoryTranslator.java | 2 +- .../BeaconInventoryTranslator.java | 2 +- .../CartographyInventoryTranslator.java | 2 +- .../CraftingInventoryTranslator.java | 2 +- .../EnchantingInventoryTranslator.java | 2 +- .../GenericBlockInventoryTranslator.java | 2 +- .../LecternInventoryTranslator.java | 2 +- .../translators/LoomInventoryTranslator.java | 2 +- .../MerchantInventoryTranslator.java | 2 +- .../ShulkerInventoryTranslator.java | 2 +- .../StonecutterInventoryTranslator.java | 2 +- .../BlastFurnaceInventoryTranslator.java | 2 +- .../furnace/FurnaceInventoryTranslator.java | 2 +- .../furnace/SmokerInventoryTranslator.java | 2 +- .../AbstractHorseInventoryTranslator.java | 2 +- .../ChestedHorseInventoryTranslator.java | 2 +- .../horse/DonkeyInventoryTranslator.java | 2 +- .../horse/HorseInventoryTranslator.java | 2 +- .../horse/LlamaInventoryTranslator.java | 2 +- .../updater/HorseInventoryUpdater.java | 2 +- .../window/JavaOpenHorseWindowTranslator.java | 2 +- .../entity/BeaconBlockEntityTranslator.java | 2 +- 35 files changed, 103 insertions(+), 107 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java index aba360a0d..02e1c225e 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/AnvilContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/inventory/CartographyContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/CartographyContainer.java index 7e6d1cc79..be4abd9e1 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/CartographyContainer.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/CartographyContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Container.java b/connector/src/main/java/org/geysermc/connector/inventory/Container.java index 9768520bd..520a76ef3 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Container.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Container.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java index ab0e544d2..8638e6eab 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java index ae4a9cf4e..a643fc194 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java index 46cf8529a..a5cfb46ca 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java @@ -1,27 +1,26 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser */ package org.geysermc.connector.inventory; diff --git a/connector/src/main/java/org/geysermc/connector/inventory/LecternContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/LecternContainer.java index 0ce9217d7..1b686a8f3 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/LecternContainer.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/LecternContainer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java index a33f81477..f4f2d90eb 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/MerchantContainer.java @@ -1,27 +1,26 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser */ package org.geysermc.connector.inventory; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java index 65083ddef..03aaf3ba8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java @@ -1,27 +1,26 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser */ package org.geysermc.connector.network.translators.bedrock; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java index 1ba08646c..832d13471 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BedrockContainerSlot.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BedrockContainerSlot.java index b3a09e167..47d1f0709 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BedrockContainerSlot.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BedrockContainerSlot.java @@ -1,27 +1,26 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser */ package org.geysermc.connector.network.translators.inventory; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java index d27290bff..fe4ac8bf9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java index 5bfe197e5..e2d554e2f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java index d4684057c..b131544b2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java index 6407b9b39..5b31c83ba 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java index 9c4eee1f2..047944336 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java index 77eeee17b..359a4559e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java index 50e8ec6ac..4d105d423 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java index b54278bcf..55df41c1e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java index 0eabf951f..9ce0ee0d1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java index 7fbca6e92..bac9e7ae6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java index c19a81be6..7422c7e48 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/ShulkerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/ShulkerInventoryTranslator.java index a0c4383ef..43584df41 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/ShulkerInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/ShulkerInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java index 419e80603..fe21969d0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/BlastFurnaceInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/BlastFurnaceInventoryTranslator.java index b7834dde9..ed9a8a79c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/BlastFurnaceInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/BlastFurnaceInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/FurnaceInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/FurnaceInventoryTranslator.java index f3d759412..b41c9b03b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/FurnaceInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/FurnaceInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/SmokerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/SmokerInventoryTranslator.java index 75eb33d97..2b9a78c7d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/SmokerInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/furnace/SmokerInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java index b33542d39..6c6c9a0c2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java index 3031e45a8..f74c2d361 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/DonkeyInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/DonkeyInventoryTranslator.java index 61a8d6926..bf13bd6da 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/DonkeyInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/DonkeyInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/HorseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/HorseInventoryTranslator.java index 957933a02..09a8f5de3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/HorseInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/HorseInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/LlamaInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/LlamaInventoryTranslator.java index 523a1d3e7..cea605f83 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/LlamaInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/LlamaInventoryTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java index 2f88c49cd..838e59d78 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java index 3d205d2a9..e5748616d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java index f60b0daab..147651a18 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BeaconBlockEntityTranslator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal From 528a9a4431bd994f83d880297c823dc330f4e7d4 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Fri, 1 Jan 2021 18:37:33 -0900 Subject: [PATCH 032/161] Crafting table slot mappings --- .../translators/CraftingInventoryTranslator.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java index 359a4559e..81769c00a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java @@ -67,4 +67,12 @@ public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslato } return super.bedrockSlotToJava(slotInfoData); } + + @Override + public int javaSlotToBedrock(int slot) { + if (slot < size) { + return slot == 0 ? 50 : slot + 31; + } + return super.javaSlotToBedrock(slot); + } } From 8928d554a1a8dde5e4d826afbb809b57358b409f Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Sun, 3 Jan 2021 17:54:26 -0900 Subject: [PATCH 033/161] WIP autocrafting using java recipe book work in progress. many edge cases are currently unhandled. will not work at all pre 1.12. (support is planned) --- .../network/session/GeyserSession.java | 3 + .../inventory/InventoryTranslator.java | 86 ++++++++++++++----- .../translators/inventory/click/Click.java | 6 +- .../inventory/click/ClickPlan.java | 16 +++- .../java/JavaDeclareRecipesTranslator.java | 1 + .../java/JavaUnlockRecipesTranslator.java | 48 +++++++++++ 6 files changed, 130 insertions(+), 30 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/java/JavaUnlockRecipesTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 10a58360c..e2587ee08 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -61,6 +61,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2LongMap; import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectIterator; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import lombok.AccessLevel; import lombok.Getter; import lombok.NonNull; @@ -230,6 +231,7 @@ public class GeyserSession implements CommandSender { @Setter private Int2ObjectMap craftingRecipes; + private final Set unlockedRecipes; /** * Saves a list of all stonecutter recipes, for use in a stonecutter inventory. @@ -382,6 +384,7 @@ public class GeyserSession implements CommandSender { this.openInventory = null; this.inventoryFuture = CompletableFuture.completedFuture(null); this.craftingRecipes = new Int2ObjectOpenHashMap<>(); + this.unlockedRecipes = new ObjectOpenHashSet<>(); this.spawned = false; this.loggedIn = false; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index ffc8d02fa..bf2fb36cf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -27,8 +27,12 @@ package org.geysermc.connector.network.translators.inventory; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientPrepareCraftingGridPacket; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; @@ -552,7 +556,8 @@ public abstract class InventoryTranslator { int recipeId = 0; int resultSize = 0; - boolean autoCraft; + int timesCrafted = 0; + boolean autoCraft = false; CraftState craftState = CraftState.START; int leftover = 0; @@ -566,28 +571,30 @@ public abstract class InventoryTranslator { } craftState = CraftState.RECIPE_ID; recipeId = craftAction.getRecipeNetworkId(); - //System.out.println(session.getCraftingRecipes().get(recipeId)); autoCraft = false; break; } -// case CRAFT_RECIPE_AUTO: { -// AutoCraftRecipeStackRequestActionData autoCraftAction = (AutoCraftRecipeStackRequestActionData) action; -// if (craftState != CraftState.START) { -// return rejectRequest(request); -// } -// craftState = CraftState.RECIPE_ID; -// recipeId = autoCraftAction.getRecipeNetworkId(); -// Recipe recipe = session.getCraftingRecipes().get(recipeId); -// System.out.println(recipe); -// if (recipe == null) { -// return rejectRequest(request); -// } -//// ClientPrepareCraftingGridPacket packet = new ClientPrepareCraftingGridPacket(session.getOpenInventory().getId(), recipe.getIdentifier(), true); -//// session.sendDownstreamPacket(packet); -// autoCraft = true; -// //TODO: reject transaction if crafting grid is not clear -// break; -// } + case CRAFT_RECIPE_AUTO: { + AutoCraftRecipeStackRequestActionData autoCraftAction = (AutoCraftRecipeStackRequestActionData) action; + if (craftState != CraftState.START) { + return rejectRequest(request); + } + craftState = CraftState.RECIPE_ID; + + recipeId = autoCraftAction.getRecipeNetworkId(); + if (!plan.getCursor().isEmpty()) { + return rejectRequest(request); + } + //reject if crafting grid is not clear + int gridSize = inventory.getId() == 0 ? 4 : 9; + for (int i = 1; i <= gridSize; i++) { + if (!inventory.getItem(i).isEmpty()) { + return rejectRequest(request); + } + } + autoCraft = true; + break; + } case CRAFT_RESULTS_DEPRECATED: { CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action; if (craftState != CraftState.RECIPE_ID) { @@ -599,7 +606,8 @@ public abstract class InventoryTranslator { return rejectRequest(request); } resultSize = deprecatedCraftAction.getResultItems()[0].getCount(); - if (resultSize <= 0) { + timesCrafted = deprecatedCraftAction.getTimesCrafted(); + if (resultSize <= 0 || timesCrafted <= 0) { return rejectRequest(request); } break; @@ -628,11 +636,45 @@ public abstract class InventoryTranslator { } int sourceSlot = bedrockSlotToJava(transferAction.getSource()); + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + + if (autoCraft) { + Recipe recipe = session.getCraftingRecipes().get(recipeId); + //cannot use java recipe book if recipe is locked + if (recipe == null || !session.getUnlockedRecipes().contains(recipe.getIdentifier())) { + return rejectRequest(request); + } + + boolean cursorDest = isCursor(transferAction.getDestination()); + boolean makeAll = timesCrafted > 1; + if (cursorDest) { + makeAll = false; + } + + ClientPrepareCraftingGridPacket prepareCraftingPacket = new ClientPrepareCraftingGridPacket(inventory.getId(), recipe.getIdentifier(), makeAll); + session.sendDownstreamPacket(prepareCraftingPacket); + + ItemStack output = null; + switch (recipe.getType()) { + case CRAFTING_SHAPED: + output = ((ShapedRecipeData)recipe.getData()).getResult(); + break; + case CRAFTING_SHAPELESS: + output = ((ShapelessRecipeData)recipe.getData()).getResult(); + break; + } + inventory.setItem(0, GeyserItemStack.from(output), session); + + plan.add(cursorDest ? Click.LEFT : Click.LEFT_SHIFT, 0); + plan.execute(true); + + return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); + } + if (isCursor(transferAction.getDestination())) { plan.add(Click.LEFT, sourceSlot); craftState = CraftState.DONE; } else { - int destSlot = bedrockSlotToJava(transferAction.getDestination()); if (leftover != 0) { if (transferAction.getCount() > leftover) { return rejectRequest(request); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java index fe4ac8bf9..d3666a9e9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/Click.java @@ -25,16 +25,14 @@ package org.geysermc.connector.network.translators.inventory.click; -import com.github.steveice10.mc.protocol.data.game.window.ClickItemParam; -import com.github.steveice10.mc.protocol.data.game.window.DropItemParam; -import com.github.steveice10.mc.protocol.data.game.window.WindowAction; -import com.github.steveice10.mc.protocol.data.game.window.WindowActionParam; +import com.github.steveice10.mc.protocol.data.game.window.*; import lombok.AllArgsConstructor; @AllArgsConstructor public enum Click { LEFT(WindowAction.CLICK_ITEM, ClickItemParam.LEFT_CLICK), RIGHT(WindowAction.CLICK_ITEM, ClickItemParam.RIGHT_CLICK), + LEFT_SHIFT(WindowAction.SHIFT_CLICK_ITEM, ShiftClickItemParam.LEFT_CLICK), DROP_ONE(WindowAction.DROP_ITEM, DropItemParam.DROP_FROM_SELECTED), DROP_ALL(WindowAction.DROP_ITEM, DropItemParam.DROP_SELECTED_STACK), LEFT_OUTSIDE(WindowAction.CLICK_ITEM, ClickItemParam.LEFT_CLICK), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java index 830a9ce0b..9268cbf12 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java @@ -166,10 +166,15 @@ public class ClickPlan { GeyserItemStack clicked = simulating ? getItem(action.slot) : inventory.getItem(action.slot); if (translator.getSlotType(action.slot) == SlotType.OUTPUT) { - if (cursor.isEmpty() && !clicked.isEmpty()) { - setCursor(clicked.copy()); - } else if (InventoryUtils.canStack(cursor, clicked)) { - cursor.add(clicked.getAmount()); + switch (action.click) { + case LEFT: + case RIGHT: + if (cursor.isEmpty() && !clicked.isEmpty()) { + setCursor(clicked.copy()); + } else if (InventoryUtils.canStack(cursor, clicked)) { + cursor.add(clicked.getAmount()); + } + break; } } else { switch (action.click) { @@ -195,6 +200,9 @@ public class ClickPlan { clicked.add(1); } break; + case LEFT_SHIFT: + //TODO + break; case DROP_ONE: if (!clicked.isEmpty()) { clicked.sub(1); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index ed0abe108..31726cc5c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -183,6 +183,7 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator { + + @Override + public void translate(ServerUnlockRecipesPacket packet, GeyserSession session) { + if (packet.getAction() == UnlockRecipesAction.REMOVE) { + session.getUnlockedRecipes().removeAll(Arrays.asList(packet.getRecipes())); + } else { + session.getUnlockedRecipes().addAll(Arrays.asList(packet.getRecipes())); + } + } +} + From 50f295b4cd6a441a0ebd46cd0f83de72b390e9ba Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 4 Jan 2021 18:19:43 -0500 Subject: [PATCH 034/161] Remove unneeded GeyserItemStack constructor and add comments --- .../org/geysermc/connector/inventory/GeyserItemStack.java | 6 +----- .../network/translators/inventory/InventoryTranslator.java | 4 ++-- .../translators/StonecutterInventoryTranslator.java | 2 +- .../translators/java/JavaDeclareRecipesTranslator.java | 5 +++++ .../translators/java/JavaUnlockRecipesTranslator.java | 3 +++ 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java index 6c6cf9110..76f7674a9 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java @@ -91,11 +91,7 @@ public class GeyserItemStack { } public static GeyserItemStack from(ItemStack itemStack) { - return from(itemStack, 1); - } - - public static GeyserItemStack from(ItemStack itemStack, int netId) { - return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getNbt(), netId); + return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getNbt()); } public ItemStack getItemStack() { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index bf2fb36cf..f0f734fcf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -757,7 +757,7 @@ public abstract class InventoryTranslator { ItemStack javaCreativeItem = ItemTranslator.translateToJava(creativeItem); if (isCursor(transferAction.getDestination())) { - session.getPlayerInventory().setCursor(GeyserItemStack.from(javaCreativeItem, session.getNextItemNetId()), session); + session.getPlayerInventory().setCursor(GeyserItemStack.from(javaCreativeItem), session); return acceptRequest(request, Collections.singletonList( new ItemStackResponsePacket.ContainerEntry(ContainerSlotType.CURSOR, Collections.singletonList(makeItemEntry(0, session.getPlayerInventory().getCursor()))))); @@ -769,7 +769,7 @@ public abstract class InventoryTranslator { existingItem.setAmount(existingItem.getAmount() + transferAction.getCount()); javaCreativeItem = existingItem.getItemStack(); } else { - inventory.setItem(javaSlot, GeyserItemStack.from(javaCreativeItem, session.getNextItemNetId()), session); + inventory.setItem(javaSlot, GeyserItemStack.from(javaCreativeItem), session); } ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( javaSlot, diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java index fe21969d0..0168d0ef9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java @@ -86,7 +86,7 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl container.setStonecutterButton(button); if (inventory.getItem(1).getJavaId() != javaOutput.getId()) { // We don't know there is an output here, so we tell ourselves that there is - inventory.setItem(1, GeyserItemStack.from(javaOutput, session.getNextItemNetId()), session); + inventory.setItem(1, GeyserItemStack.from(javaOutput), session); } } return translateRequest(session, inventory, request); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index 31726cc5c..2565c6abb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -47,6 +47,11 @@ import org.geysermc.connector.network.translators.item.*; import java.util.*; import java.util.stream.Collectors; +/** + * Used to send all valid recipes from Java to Bedrock. + * + * Bedrock REQUIRES a CraftingDataPacket to be sent in order to craft anything. + */ @Translator(packet = ServerDeclareRecipesPacket.class) public class JavaDeclareRecipesTranslator extends PacketTranslator { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaUnlockRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaUnlockRecipesTranslator.java index 6b6aefc33..0a0ba4d2d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaUnlockRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaUnlockRecipesTranslator.java @@ -33,6 +33,9 @@ import org.geysermc.connector.network.translators.Translator; import java.util.Arrays; +/** + * Used to list recipes that we can definitely use the recipe book for (and therefore save on packet usage) + */ @Translator(packet = ServerUnlockRecipesPacket.class) public class JavaUnlockRecipesTranslator extends PacketTranslator { From a160e3694b2574c8214fac796f80d84672fd600d Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Mon, 4 Jan 2021 14:47:48 -0900 Subject: [PATCH 035/161] Add stackSize to ItemEntry --- .../network/translators/item/ItemEntry.java | 3 ++- .../network/translators/item/ItemRegistry.java | 12 ++++++++---- .../network/translators/item/ToolItemEntry.java | 4 ++-- connector/src/main/resources/mappings | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java index f61c3d709..278d708f9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemEntry.java @@ -34,7 +34,7 @@ import lombok.ToString; @ToString public class ItemEntry { - public static ItemEntry AIR = new ItemEntry("minecraft:air", "minecraft:air", 0, 0, 0, false); + public static ItemEntry AIR = new ItemEntry("minecraft:air", "minecraft:air", 0, 0, 0, false, 64); private final String javaIdentifier; private final String bedrockIdentifier; @@ -43,6 +43,7 @@ public class ItemEntry { private final int bedrockData; private final boolean block; + private final int stackSize; @Override public boolean equals(Object obj) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index 02cd839aa..c2b60c8f4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -147,6 +147,7 @@ public class ItemRegistry { if (bedrockIdentifier == null) { throw new RuntimeException("Missing Bedrock ID in mappings!: " + bedrockId); } + int stackSize = entry.getValue().get("stack_size") == null ? 64 : entry.getValue().get("stack_size").intValue(); if (entry.getValue().has("tool_type")) { if (entry.getValue().has("tool_tier")) { ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( @@ -154,19 +155,22 @@ public class ItemRegistry { entry.getValue().get("bedrock_data").intValue(), entry.getValue().get("tool_type").textValue(), entry.getValue().get("tool_tier").textValue(), - entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue())); + entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue(), + stackSize)); } else { ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getValue().get("bedrock_data").intValue(), entry.getValue().get("tool_type").textValue(), - "", entry.getValue().get("is_block").booleanValue())); + "", entry.getValue().get("is_block").booleanValue(), + stackSize)); } } else { ITEM_ENTRIES.put(itemIndex, new ItemEntry( entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getValue().get("bedrock_data").intValue(), - entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue())); + entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue(), + stackSize)); } switch (entry.getKey()) { case "minecraft:barrier": @@ -209,7 +213,7 @@ public class ItemRegistry { // Add the loadstone compass since it doesn't exist on java but we need it for item conversion ITEM_ENTRIES.put(itemIndex, new ItemEntry("minecraft:lodestone_compass", "minecraft:lodestone_compass", itemIndex, - lodestoneCompassId, 0, false)); + lodestoneCompassId, 0, false, 1)); /* Load creative items */ stream = FileUtils.getResource("bedrock/creative_items.json"); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java index 5352938c0..ba1753a35 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java @@ -32,8 +32,8 @@ public class ToolItemEntry extends ItemEntry { private final String toolType; private final String toolTier; - public ToolItemEntry(String javaIdentifier, String bedrockIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier, boolean isBlock) { - super(javaIdentifier, bedrockIdentifier, javaId, bedrockId, bedrockData, isBlock); + public ToolItemEntry(String javaIdentifier, String bedrockIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier, boolean isBlock, int stackSize) { + super(javaIdentifier, bedrockIdentifier, javaId, bedrockId, bedrockData, isBlock, stackSize); this.toolType = toolType; this.toolTier = toolTier; } diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 143285afb..62e39acaf 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 143285afb4bdf4d5ef40ef7a7959477dabf4d34c +Subproject commit 62e39acaf3859da86189a76fff57b30c2403ef3d From 57e176efd6572f56fc8fe3921fd32aa8b255d9f0 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Mon, 4 Jan 2021 15:12:26 -0900 Subject: [PATCH 036/161] Optimize stackSize Co-Authored-By: Camotoy <20743703+Camotoy@users.noreply.github.com> --- .../connector/network/translators/item/ItemRegistry.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index c2b60c8f4..adb91aa3e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -147,7 +147,8 @@ public class ItemRegistry { if (bedrockIdentifier == null) { throw new RuntimeException("Missing Bedrock ID in mappings!: " + bedrockId); } - int stackSize = entry.getValue().get("stack_size") == null ? 64 : entry.getValue().get("stack_size").intValue(); + JsonNode stackSizeNode = entry.getValue().get("stack_size"); + int stackSize = stackSizeNode == null ? 64 : stackSizeNode.intValue(); if (entry.getValue().has("tool_type")) { if (entry.getValue().has("tool_tier")) { ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( From ff69752d2cd434985dd44c74d960852aae5363ad Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Mon, 4 Jan 2021 15:16:21 -0900 Subject: [PATCH 037/161] Remove unnecessary is_block null check --- .../connector/network/translators/item/ItemRegistry.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index adb91aa3e..dea9b5c9a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -156,7 +156,7 @@ public class ItemRegistry { entry.getValue().get("bedrock_data").intValue(), entry.getValue().get("tool_type").textValue(), entry.getValue().get("tool_tier").textValue(), - entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue(), + entry.getValue().get("is_block").booleanValue(), stackSize)); } else { ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( @@ -170,7 +170,7 @@ public class ItemRegistry { ITEM_ENTRIES.put(itemIndex, new ItemEntry( entry.getKey(), bedrockIdentifier, itemIndex, bedrockId, entry.getValue().get("bedrock_data").intValue(), - entry.getValue().get("is_block") != null && entry.getValue().get("is_block").booleanValue(), + entry.getValue().get("is_block").booleanValue(), stackSize)); } switch (entry.getKey()) { From a88678a5c10ecdaadb75cec318306b48260fe1c1 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 4 Jan 2021 22:15:55 -0500 Subject: [PATCH 038/161] Add manual recipes as Recipe classes for future usage --- .../translators/item/ItemRegistry.java | 2 +- .../translators/item/RecipeRegistry.java | 92 ++++++++++++++++--- .../translators/TippedArrowTranslator.java | 1 + .../java/JavaDeclareRecipesTranslator.java | 2 +- connector/src/main/resources/mappings | 2 +- 5 files changed, 84 insertions(+), 15 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index dea9b5c9a..4237f7d6a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -56,7 +56,7 @@ public class ItemRegistry { * A list of all identifiers that only exist on Java. Used to prevent creative items from becoming these unintentionally. */ private static final List JAVA_ONLY_ITEMS = Arrays.asList("minecraft:spectral_arrow", "minecraft:debug_stick", - "minecraft:knowledge_book"); + "minecraft:knowledge_book", "minecraft:tipped_arrow", "minecraft:furnace_minecart"); public static final ItemData[] CREATIVE_ITEMS; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java index 4c6a872f5..cb866e6b1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java @@ -26,13 +26,25 @@ package org.geysermc.connector.network.translators.item; import com.fasterxml.jackson.databind.JsonNode; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; +import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; +import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtUtils; import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; import java.util.*; @@ -47,7 +59,7 @@ public class RecipeRegistry { */ public static int LAST_RECIPE_NET_ID = 0; - //public static final Int2ObjectMap + public static final Int2ObjectMap ALL_CRAFTING_RECIPES = new Int2ObjectOpenHashMap<>(); /** * A list of all possible leather armor dyeing recipes. @@ -131,7 +143,7 @@ public class RecipeRegistry { throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.toolbox.fail.runtime_java"), e); } - for (JsonNode entry: items.get("leather_armor")) { + for (JsonNode entry : items.get("leather_armor")) { // This won't be perfect, as we can't possibly send every leather input for every kind of color // But it does display the correct output from a base leather armor, and besides visuals everything works fine LEATHER_DYEING_RECIPES.add(getCraftingDataFromJsonNode(entry)); @@ -151,6 +163,7 @@ public class RecipeRegistry { for (JsonNode entry : items.get("tipped_arrows")) { TIPPED_ARROW_RECIPES.add(getCraftingDataFromJsonNode(entry)); } + System.out.println(ALL_CRAFTING_RECIPES); } /** @@ -159,9 +172,13 @@ public class RecipeRegistry { * @return the {@link CraftingData} to send to the Bedrock client. */ private static CraftingData getCraftingDataFromJsonNode(JsonNode node) { - ItemData output = ItemRegistry.getBedrockItemFromJson(node.get("output").get(0)); + int netId = LAST_RECIPE_NET_ID++; + int type = node.get("bedrockRecipeType").asInt(); + JsonNode outputNode = node.get("output"); + ItemEntry outputEntry = ItemRegistry.getItemEntry(outputNode.get("identifier").asText()); + ItemData output = getBedrockItemFromIdentifierJson(outputEntry, outputNode); UUID uuid = UUID.randomUUID(); - if (node.get("type").asInt() == 1) { + if (type == 1) { // Shaped recipe List shape = new ArrayList<>(); // Get the shape of the recipe @@ -171,10 +188,12 @@ public class RecipeRegistry { // In recipes.json each recipe is mapped by a letter Map letterToRecipe = new HashMap<>(); - Iterator> iterator = node.get("input").fields(); + Iterator> iterator = node.get("inputs").fields(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); - letterToRecipe.put(entry.getKey(), ItemRegistry.getBedrockItemFromJson(entry.getValue())); + JsonNode inputNode = entry.getValue(); + ItemEntry inputEntry = ItemRegistry.getItemEntry(inputNode.get("identifier").asText()); + letterToRecipe.put(entry.getKey(), getBedrockItemFromIdentifierJson(inputEntry, inputNode)); } List inputs = new ArrayList<>(shape.size() * shape.get(0).length()); @@ -188,20 +207,69 @@ public class RecipeRegistry { } } + /* Convert into a Java recipe class for autocrafting */ + List ingredients = new ArrayList<>(); + for (ItemData input : inputs) { + ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input)})); + } + ShapedRecipeData data = new ShapedRecipeData(shape.get(0).length(), shape.size(), "crafting_table", + ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output)); + Recipe recipe = new Recipe(RecipeType.CRAFTING_SHAPED, "", data); + ALL_CRAFTING_RECIPES.put(netId, recipe); + /* Convert end */ + return CraftingData.fromShaped(uuid.toString(), shape.get(0).length(), shape.size(), - inputs, Collections.singletonList(output), uuid, "crafting_table", 0, LAST_RECIPE_NET_ID++); + inputs, Collections.singletonList(output), uuid, "crafting_table", 0, netId); } List inputs = new ObjectArrayList<>(); - for (JsonNode entry : node.get("input")) { - inputs.add(ItemRegistry.getBedrockItemFromJson(entry)); + for (JsonNode entry : node.get("inputs")) { + ItemEntry inputEntry = ItemRegistry.getItemEntry(entry.get("identifier").asText()); + inputs.add(getBedrockItemFromIdentifierJson(inputEntry, entry)); } - if (node.get("type").asInt() == 5) { + + /* Convert into a Java Recipe class for autocrafting */ + List ingredients = new ArrayList<>(); + for (ItemData input : inputs) { + ingredients.add(new Ingredient(new ItemStack[]{ItemTranslator.translateToJava(input)})); + } + ShapelessRecipeData data = new ShapelessRecipeData("crafting_table", + ingredients.toArray(new Ingredient[0]), ItemTranslator.translateToJava(output)); + Recipe recipe = new Recipe(RecipeType.CRAFTING_SHAPELESS, "", data); + ALL_CRAFTING_RECIPES.put(netId, recipe); + /* Convert end */ + + if (type == 5) { // Shulker box return CraftingData.fromShulkerBox(uuid.toString(), - inputs, Collections.singletonList(output), uuid, "crafting_table", 0, LAST_RECIPE_NET_ID++); + inputs, Collections.singletonList(output), uuid, "crafting_table", 0, netId); } return CraftingData.fromShapeless(uuid.toString(), - inputs, Collections.singletonList(output), uuid, "crafting_table", 0, LAST_RECIPE_NET_ID++); + inputs, Collections.singletonList(output), uuid, "crafting_table", 0, netId); + } + + private static ItemData getBedrockItemFromIdentifierJson(ItemEntry itemEntry, JsonNode itemNode) { + int count = 1; + short damage = 0; + NbtMap tag = null; + JsonNode damageNode = itemNode.get("bedrockDamage"); + if (damageNode != null) { + damage = damageNode.numberValue().shortValue(); + } + JsonNode countNode = itemNode.get("count"); + if (countNode != null) { + count = countNode.asInt(); + } + JsonNode nbtNode = itemNode.get("bedrockNbt"); + if (nbtNode != null) { + byte[] bytes = Base64.getDecoder().decode(nbtNode.asText()); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + try { + tag = (NbtMap) NbtUtils.createReaderLE(bais).readTag(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return ItemData.of(itemEntry.getBedrockId(), damage, count, tag); } public static void init() { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java index dd151dcd8..c33b71ac5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java @@ -70,6 +70,7 @@ public class TippedArrowTranslator extends ItemTranslator { @Override public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { + GeyserConnector.getInstance().getLogger().warning(itemData.toString() + " " + itemEntry.getJavaIdentifier()); TippedArrowPotion tippedArrowPotion = TippedArrowPotion.getByBedrockId(itemData.getDamage()); ItemStack itemStack = super.translateToJava(itemData, itemEntry); if (tippedArrowPotion != null) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index 2565c6abb..3b4f14d71 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -59,7 +59,7 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator recipeMap = new Int2ObjectOpenHashMap<>(); + Int2ObjectMap recipeMap = new Int2ObjectOpenHashMap<>(RecipeRegistry.ALL_CRAFTING_RECIPES); Int2ObjectMap> unsortedStonecutterData = new Int2ObjectOpenHashMap<>(); CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); craftingDataPacket.setCleanRecipes(true); diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 62e39acaf..07f65c380 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 62e39acaf3859da86189a76fff57b30c2403ef3d +Subproject commit 07f65c3803dcd3f83358ee574e54bf129cad0840 From c8016647f21c67ee2f7452c30b5be9a40a65610c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 5 Jan 2021 19:08:54 -0500 Subject: [PATCH 039/161] Clean up and add mobile button for horse opening --- .../entity/player/BedrockInteractTranslator.java | 12 +++++++++--- .../network/translators/item/RecipeRegistry.java | 5 ++++- .../item/translators/TippedArrowTranslator.java | 1 - 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java index c7c596d8d..ca71a1975 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java @@ -39,6 +39,7 @@ import com.nukkitx.protocol.bedrock.packet.InteractPacket; import lombok.Getter; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.living.animal.horse.AbstractHorseEntity; +import org.geysermc.connector.entity.living.animal.horse.HorseEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.network.session.GeyserSession; @@ -212,6 +213,11 @@ public class BedrockInteractTranslator extends PacketTranslator case SKELETON_HORSE: case TRADER_LLAMA: case ZOMBIE_HORSE: + boolean tamed = entityMetadata.getFlags().getFlag(EntityFlag.TAMED); + if (session.isSneaking() && tamed && (interactEntity instanceof HorseEntity || entityMetadata.getFlags().getFlag(EntityFlag.CHESTED))) { + interactiveTag = InteractiveTag.OPEN_CONTAINER; + break; + } // have another switch statement as, while these share mount attributes they don't share food switch (interactEntity.getEntityType()) { case LLAMA: @@ -230,9 +236,9 @@ public class BedrockInteractTranslator extends PacketTranslator } if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY)) { // Can't ride a baby - if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { + if (tamed) { interactiveTag = InteractiveTag.RIDE_HORSE; - } else if (!entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && itemEntry.equals(ItemEntry.AIR)) { + } else if (itemEntry.equals(ItemEntry.AIR)) { // Can't hide an untamed entity without having your hand empty interactiveTag = InteractiveTag.MOUNT; } @@ -351,7 +357,7 @@ public class BedrockInteractTranslator extends PacketTranslator } else { if (!session.getPlayerEntity().getMetadata().getString(EntityData.INTERACTIVE_TAG).isEmpty()) { // No interactive tag should be sent - session.getPlayerEntity().getMetadata().remove(EntityData.INTERACTIVE_TAG); + session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, ""); session.getPlayerEntity().updateBedrockMetadata(session); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java index cb866e6b1..110014bf5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/RecipeRegistry.java @@ -59,6 +59,10 @@ public class RecipeRegistry { */ public static int LAST_RECIPE_NET_ID = 0; + /** + * A list of all the following crafting recipes, but in a format understood by Java servers. + * Used for console autocrafting. + */ public static final Int2ObjectMap ALL_CRAFTING_RECIPES = new Int2ObjectOpenHashMap<>(); /** @@ -163,7 +167,6 @@ public class RecipeRegistry { for (JsonNode entry : items.get("tipped_arrows")) { TIPPED_ARROW_RECIPES.add(getCraftingDataFromJsonNode(entry)); } - System.out.println(ALL_CRAFTING_RECIPES); } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java index c33b71ac5..dd151dcd8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/TippedArrowTranslator.java @@ -70,7 +70,6 @@ public class TippedArrowTranslator extends ItemTranslator { @Override public ItemStack translateToJava(ItemData itemData, ItemEntry itemEntry) { - GeyserConnector.getInstance().getLogger().warning(itemData.toString() + " " + itemEntry.getJavaIdentifier()); TippedArrowPotion tippedArrowPotion = TippedArrowPotion.getByBedrockId(itemData.getDamage()); ItemStack itemStack = super.translateToJava(itemData, itemEntry); if (tippedArrowPotion != null) { From 9118ec68406e6ff8c0498c9253f43bc6d3adc9aa Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Fri, 8 Jan 2021 17:01:31 -0900 Subject: [PATCH 040/161] wip support old crafting --- .../network/session/GeyserSession.java | 2 + .../inventory/InventoryTranslator.java | 7 +-- .../java/JavaDeclareRecipesTranslator.java | 3 +- .../java/window/JavaSetSlotTranslator.java | 62 +++++++++++++++++++ 4 files changed, 69 insertions(+), 5 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index e2587ee08..4287c24f6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -232,6 +232,7 @@ public class GeyserSession implements CommandSender { @Setter private Int2ObjectMap craftingRecipes; private final Set unlockedRecipes; + private AtomicInteger lastRecipeNetId; /** * Saves a list of all stonecutter recipes, for use in a stonecutter inventory. @@ -385,6 +386,7 @@ public class GeyserSession implements CommandSender { this.inventoryFuture = CompletableFuture.completedFuture(null); this.craftingRecipes = new Int2ObjectOpenHashMap<>(); this.unlockedRecipes = new ObjectOpenHashSet<>(); + this.lastRecipeNetId = new AtomicInteger(1); this.spawned = false; this.loggedIn = false; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index f0f734fcf..4c1c55976 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -805,13 +805,12 @@ public abstract class InventoryTranslator { } public boolean checkNetId(GeyserSession session, Inventory inventory, StackRequestSlotInfoData slotInfoData) { - if (slotInfoData.getStackNetworkId() < 0) + int netId = slotInfoData.getStackNetworkId(); + if (netId < 0 || netId == 1) return true; -// if (slotInfoData.getContainer() == ContainerSlotType.CURSOR) //TODO: temporary -// return true; GeyserItemStack currentItem = isCursor(slotInfoData) ? session.getPlayerInventory().getCursor() : inventory.getItem(bedrockSlotToJava(slotInfoData)); - return currentItem.getNetId() == slotInfoData.getStackNetworkId(); + return currentItem.getNetId() == netId; } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index 3b4f14d71..7624bf868 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -186,10 +186,11 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator { @@ -55,6 +67,56 @@ public class JavaSetSlotTranslator extends PacketTranslator InventoryTranslator translator = session.getInventoryTranslator(); if (translator != null) { + if (packet.getSlot() == 0) { + int gridSize = -1; + if (translator instanceof PlayerInventoryTranslator) { + gridSize = 4; + } + if (translator instanceof CraftingInventoryTranslator) { + gridSize = 9; + } + if (gridSize != -1) { + int offset = gridSize == 4 ? 28 : 32; + int gridWidth = gridSize == 4 ? 2 : 3; + ItemData[] ingredients = new ItemData[gridSize]; + //construct ingredient list and clear slots on client + for (int i = 0; i < gridSize; i++) { + ingredients[i] = inventory.getItem(i + 1).getItemData(session); + + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(i + offset); + slotPacket.setItem(ItemData.AIR); + session.sendUpstreamPacket(slotPacket); + } + + CraftingDataPacket craftPacket = new CraftingDataPacket(); + UUID uuid = UUID.fromString("e0a4971a-698c-40fb-95dd-afc8ed16e108"); + craftPacket.getCraftingData().add(CraftingData.fromShaped( + uuid.toString(), + gridWidth, + gridWidth, + Arrays.asList(ingredients), + Collections.singletonList(ItemTranslator.translateToBedrock(session, packet.getItem())), + uuid, + "crafting_table", + 0, + session.getLastRecipeNetId().incrementAndGet() + )); + craftPacket.setCleanRecipes(false); + session.sendUpstreamPacket(craftPacket); + + //restore cleared slots + for (int i = 0; i < gridSize; i++) { + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(i + offset); + slotPacket.setItem(ingredients[i]); + session.sendUpstreamPacket(slotPacket); + } + } + } + GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); inventory.setItem(packet.getSlot(), newItem, session); translator.updateSlot(session, inventory, packet.getSlot()); From 9bfc5d320ca0a9e8e36300a78727e2ae47f29a8a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 9 Jan 2021 00:38:53 -0500 Subject: [PATCH 041/161] Work-in-progress checks for legacy slot checking --- .../java/JavaDeclareRecipesTranslator.java | 2 +- .../java/window/JavaSetSlotTranslator.java | 197 +++++++++++++----- 2 files changed, 149 insertions(+), 50 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index 7624bf868..07b839f9c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -186,7 +186,7 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator InventoryTranslator translator = session.getInventoryTranslator(); if (translator != null) { - if (packet.getSlot() == 0) { - int gridSize = -1; - if (translator instanceof PlayerInventoryTranslator) { - gridSize = 4; - } - if (translator instanceof CraftingInventoryTranslator) { - gridSize = 9; - } - if (gridSize != -1) { - int offset = gridSize == 4 ? 28 : 32; - int gridWidth = gridSize == 4 ? 2 : 3; - ItemData[] ingredients = new ItemData[gridSize]; - //construct ingredient list and clear slots on client - for (int i = 0; i < gridSize; i++) { - ingredients[i] = inventory.getItem(i + 1).getItemData(session); - - InventorySlotPacket slotPacket = new InventorySlotPacket(); - slotPacket.setContainerId(ContainerId.UI); - slotPacket.setSlot(i + offset); - slotPacket.setItem(ItemData.AIR); - session.sendUpstreamPacket(slotPacket); - } - - CraftingDataPacket craftPacket = new CraftingDataPacket(); - UUID uuid = UUID.fromString("e0a4971a-698c-40fb-95dd-afc8ed16e108"); - craftPacket.getCraftingData().add(CraftingData.fromShaped( - uuid.toString(), - gridWidth, - gridWidth, - Arrays.asList(ingredients), - Collections.singletonList(ItemTranslator.translateToBedrock(session, packet.getItem())), - uuid, - "crafting_table", - 0, - session.getLastRecipeNetId().incrementAndGet() - )); - craftPacket.setCleanRecipes(false); - session.sendUpstreamPacket(craftPacket); - - //restore cleared slots - for (int i = 0; i < gridSize; i++) { - InventorySlotPacket slotPacket = new InventorySlotPacket(); - slotPacket.setContainerId(ContainerId.UI); - slotPacket.setSlot(i + offset); - slotPacket.setItem(ingredients[i]); - session.sendUpstreamPacket(slotPacket); - } - } - } + updateCraftingGrid(session, packet, inventory, translator); GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); inventory.setItem(packet.getSlot(), newItem, session); @@ -123,4 +81,145 @@ public class JavaSetSlotTranslator extends PacketTranslator } }); } + + private void updateCraftingGrid(GeyserSession session, ServerSetSlotPacket packet, Inventory inventory, InventoryTranslator translator) { + if (packet.getSlot() == 0) { + int gridSize; + if (translator instanceof PlayerInventoryTranslator) { + gridSize = 4; + } else if (translator instanceof CraftingInventoryTranslator) { + gridSize = 9; + } else { + return; + } + + if (packet.getItem() == null || packet.getItem().getId() == 0) { + return; + } + + int offset = gridSize == 4 ? 28 : 32; + int gridDimensions = gridSize == 4 ? 2 : 3; + int itemsStart = 0; + for (int i = 1; i < inventory.getSize(); i++) { // Slot 0 is, well, the output, so we ignore that + if (!inventory.getItem(i).isEmpty()) { + System.out.println(inventory.getItem(i).getItemStack().toString()); + itemsStart = i; + break; + } + } + + System.out.println("Items start: " + itemsStart); + + //TODO + recipes: + for (Recipe recipe : session.getCraftingRecipes().values()) { + if (recipe.getType() == RecipeType.CRAFTING_SHAPED) { + ShapedRecipeData data = (ShapedRecipeData) recipe.getData(); + if (!data.getResult().equals(packet.getItem())) { + continue; + } + int height = 1; + int width = 1; + for (int i = 0; i < data.getIngredients().length; i++) { + System.out.println(height); + System.out.println(width); + System.out.println(data.getHeight()); + System.out.println(data.getWidth()); + System.out.println(Arrays.toString(data.getIngredients())); + Ingredient ingredient = data.getIngredients()[i]; + GeyserItemStack geyserItemStack = inventory.getItem(itemsStart + (width - 1) + ((data.getWidth() - 1) * (gridDimensions - data.getWidth() + height))); + System.out.println(itemsStart + (width - 1) + ((data.getWidth() - 1) * (gridDimensions - data.getWidth() + height))); + boolean inventoryHasItem = false; + for (ItemStack itemStack : ingredient.getOptions()) { + if (geyserItemStack.isEmpty()) { + inventoryHasItem = itemStack == null || itemStack.getId() == 0; + if (inventoryHasItem) { + break; + } + } else if (itemStack.equals(geyserItemStack.getItemStack())) { + inventoryHasItem = true; + break; + } + } + if (!inventoryHasItem) { + break recipes; + } + width++; + if (width > data.getWidth()) { + width = 1; + height++; + } + } + // Recipe is had, don't sent packet + return; + } else if (recipe.getType() == RecipeType.CRAFTING_SHAPELESS) { + ShapelessRecipeData data = (ShapelessRecipeData) recipe.getData(); + if (!data.getResult().equals(packet.getItem())) { + continue; + } + for (int i = 0; i < data.getIngredients().length; i++) { + Ingredient ingredient = data.getIngredients()[i]; + for (ItemStack itemStack : ingredient.getOptions()) { + boolean inventoryHasItem = false; + for (int j = 0; j < inventory.getSize(); j++) { + GeyserItemStack geyserItemStack = inventory.getItem(j); + if (geyserItemStack.isEmpty()) { + inventoryHasItem = itemStack == null || itemStack.getId() == 0; + if (inventoryHasItem) { + break; + } + } else if (itemStack.equals(geyserItemStack.getItemStack())) { + inventoryHasItem = true; + break; + } + } + if (!inventoryHasItem) { + continue recipes; + } + } + } + // Recipe is had, don't sent packet + return; + } + } + System.out.println("Sending packet!"); + + ItemData[] ingredients = new ItemData[gridSize]; + //construct ingredient list and clear slots on client + for (int i = 0; i < gridSize; i++) { + ingredients[i] = inventory.getItem(i + 1).getItemData(session); + + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(i + offset); + slotPacket.setItem(ItemData.AIR); + session.sendUpstreamPacket(slotPacket); + } + + CraftingDataPacket craftPacket = new CraftingDataPacket(); + UUID uuid = UUID.randomUUID(); + craftPacket.getCraftingData().add(CraftingData.fromShaped( + uuid.toString(), + gridDimensions, + gridDimensions, + Arrays.asList(ingredients), + Collections.singletonList(ItemTranslator.translateToBedrock(session, packet.getItem())), + uuid, + "crafting_table", + 0, + session.getLastRecipeNetId().incrementAndGet() + )); + craftPacket.setCleanRecipes(false); + session.sendUpstreamPacket(craftPacket); + + //restore cleared slots + for (int i = 0; i < gridSize; i++) { + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(i + offset); + slotPacket.setItem(ingredients[i]); + session.sendUpstreamPacket(slotPacket); + } + } + } } From 554ec82ef59a3f7729914e8e926a48cf9822eb0d Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Sat, 9 Jan 2021 04:10:27 -0900 Subject: [PATCH 042/161] Shaped recipe detection --- .../connector/inventory/GeyserItemStack.java | 6 +- .../java/window/JavaSetSlotTranslator.java | 110 ++++++++++++------ 2 files changed, 78 insertions(+), 38 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java index 76f7674a9..7cdaf1801 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java @@ -95,7 +95,11 @@ public class GeyserItemStack { } public ItemStack getItemStack() { - return isEmpty() ? null : new ItemStack(javaId, amount, nbt); + return getItemStack(amount); + } + + public ItemStack getItemStack(int newAmount) { + return isEmpty() ? null : new ItemStack(javaId, newAmount, nbt); } public ItemData getItemData(GeyserSession session) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java index d30060a13..39a7db11d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java @@ -50,6 +50,7 @@ import org.geysermc.connector.utils.InventoryUtils; import java.util.Arrays; import java.util.Collections; +import java.util.Objects; import java.util.UUID; @Translator(packet = ServerSetSlotPacket.class) @@ -82,7 +83,7 @@ public class JavaSetSlotTranslator extends PacketTranslator }); } - private void updateCraftingGrid(GeyserSession session, ServerSetSlotPacket packet, Inventory inventory, InventoryTranslator translator) { + private static void updateCraftingGrid(GeyserSession session, ServerSetSlotPacket packet, Inventory inventory, InventoryTranslator translator) { if (packet.getSlot() == 0) { int gridSize; if (translator instanceof PlayerInventoryTranslator) { @@ -99,16 +100,35 @@ public class JavaSetSlotTranslator extends PacketTranslator int offset = gridSize == 4 ? 28 : 32; int gridDimensions = gridSize == 4 ? 2 : 3; - int itemsStart = 0; - for (int i = 1; i < inventory.getSize(); i++) { // Slot 0 is, well, the output, so we ignore that - if (!inventory.getItem(i).isEmpty()) { - System.out.println(inventory.getItem(i).getItemStack().toString()); - itemsStart = i; - break; + int firstRow = -1, height = -1; + int firstCol = -1, width = -1; + for (int row = 0; row < gridDimensions; row++) { + for (int col = 0; col < gridDimensions; col++) { + if (!inventory.getItem(col + (row * gridDimensions) + 1).isEmpty()) { + if (firstRow == -1) { + firstRow = row; + firstCol = col; + } else { + firstCol = Math.min(firstCol, col); + } + height = Math.max(height, row); + width = Math.max(width, col); + } } } - System.out.println("Items start: " + itemsStart); + //empty grid + if (firstRow == -1) { + return; + } + + height += -firstRow + 1; + width += -firstCol + 1; + + System.out.println("Start Row: " + firstRow); + System.out.println("Start Column: " + firstCol); + System.out.println("Rows: " + height); + System.out.println("Columns: " + width); //TODO recipes: @@ -118,38 +138,27 @@ public class JavaSetSlotTranslator extends PacketTranslator if (!data.getResult().equals(packet.getItem())) { continue; } - int height = 1; - int width = 1; - for (int i = 0; i < data.getIngredients().length; i++) { - System.out.println(height); - System.out.println(width); - System.out.println(data.getHeight()); - System.out.println(data.getWidth()); - System.out.println(Arrays.toString(data.getIngredients())); - Ingredient ingredient = data.getIngredients()[i]; - GeyserItemStack geyserItemStack = inventory.getItem(itemsStart + (width - 1) + ((data.getWidth() - 1) * (gridDimensions - data.getWidth() + height))); - System.out.println(itemsStart + (width - 1) + ((data.getWidth() - 1) * (gridDimensions - data.getWidth() + height))); - boolean inventoryHasItem = false; - for (ItemStack itemStack : ingredient.getOptions()) { - if (geyserItemStack.isEmpty()) { - inventoryHasItem = itemStack == null || itemStack.getId() == 0; - if (inventoryHasItem) { - break; - } - } else if (itemStack.equals(geyserItemStack.getItemStack())) { - inventoryHasItem = true; - break; + if (data.getWidth() != width || data.getHeight() != height || width * height != data.getIngredients().length) { + continue; + } + + Ingredient[] ingredients = data.getIngredients(); + if (!testShapedRecipe(ingredients, inventory, gridDimensions, firstRow, height, firstCol, width)) { + Ingredient[] mirroredIngredients = new Ingredient[data.getIngredients().length]; + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + mirroredIngredients[col + (row * width)] = ingredients[(width - 1 - col) + (row * width)]; } } - if (!inventoryHasItem) { - break recipes; - } - width++; - if (width > data.getWidth()) { - width = 1; - height++; + + if (Arrays.equals(ingredients, mirroredIngredients)) { + continue; + } else if (!testShapedRecipe(mirroredIngredients, inventory, gridDimensions, firstRow, height, firstCol, width)) { + continue; } } + System.out.println("FOUND SHAPED RECIPE :)"); + System.out.println(recipe); // Recipe is had, don't sent packet return; } else if (recipe.getType() == RecipeType.CRAFTING_SHAPELESS) { @@ -168,7 +177,7 @@ public class JavaSetSlotTranslator extends PacketTranslator if (inventoryHasItem) { break; } - } else if (itemStack.equals(geyserItemStack.getItemStack())) { + } else if (itemStack.equals(geyserItemStack.getItemStack(1))) { inventoryHasItem = true; break; } @@ -222,4 +231,31 @@ public class JavaSetSlotTranslator extends PacketTranslator } } } + + private static boolean testShapedRecipe(Ingredient[] ingredients, Inventory inventory, int gridDimensions, int firstRow, int height, int firstCol, int width) { + int ingredientIndex = 0; + for (int row = firstRow; row < height + firstRow; row++) { + for (int col = firstCol; col < width + firstCol; col++) { + GeyserItemStack geyserItemStack = inventory.getItem(col + (row * gridDimensions) + 1); + Ingredient ingredient = ingredients[ingredientIndex++]; + if (ingredient.getOptions().length == 0) { + if (!geyserItemStack.isEmpty()) { + return false; + } + } else { + boolean inventoryHasItem = false; + for (ItemStack item : ingredient.getOptions()) { + if (Objects.equals(geyserItemStack.getItemStack(1), item)) { + inventoryHasItem = true; + break; + } + } + if (!inventoryHasItem) { + return false; + } + } + } + } + return true; + } } From 54fb56ea85d29f8ba7b1f03d2378d581fffb8e0b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 9 Jan 2021 16:45:32 -0500 Subject: [PATCH 043/161] Add legacy crafting support for Spigot and finish up standalone --- .../platform/spigot/GeyserSpigotPlugin.java | 10 +- .../GeyserSpigot1_11CraftingListener.java | 197 ++++++++++++++++++ .../java/window/JavaSetSlotTranslator.java | 59 ++++-- 3 files changed, 244 insertions(+), 22 deletions(-) create mode 100644 bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 74b9e03d4..86247300a 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -43,6 +43,7 @@ import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.platform.spigot.command.SpigotCommandSender; +import org.geysermc.platform.spigot.world.GeyserSpigot1_11CraftingListener; import org.geysermc.platform.spigot.world.GeyserSpigotBlockPlaceListener; import org.geysermc.platform.spigot.world.manager.*; import us.myles.ViaVersion.api.Pair; @@ -146,8 +147,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserLogger.debug("Legacy version of Minecraft (1.15.2 or older) detected; not using 3D biomes."); } + boolean isPre1_12 = !isCompatible(Bukkit.getServer().getVersion(), "1.12.0"); // Set if we need to use a different method for getting a player's locale - SpigotCommandSender.setUseLegacyLocaleMethod(!isCompatible(Bukkit.getServer().getVersion(), "1.12.0")); + SpigotCommandSender.setUseLegacyLocaleMethod(isPre1_12); if (connector.getConfig().isUseAdapters()) { try { @@ -191,9 +193,13 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserLogger.debug("Using default world manager: " + this.geyserWorldManager.getClass()); } GeyserSpigotBlockPlaceListener blockPlaceListener = new GeyserSpigotBlockPlaceListener(connector, this.geyserWorldManager); - Bukkit.getServer().getPluginManager().registerEvents(blockPlaceListener, this); + if (isPre1_12) { + // Register events needed to send all recipes to the client + Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigot1_11CraftingListener(this, connector), this); + } + this.getCommand("geyser").setExecutor(new GeyserSpigotCommandExecutor(connector)); } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java new file mode 100644 index 000000000..d20b7637d --- /dev/null +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.platform.spigot.world; + +import com.github.steveice10.mc.protocol.MinecraftConstants; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; +import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; +import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; +import com.nukkitx.protocol.bedrock.data.inventory.CraftingData; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.inventory.Recipe; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.ShapelessRecipe; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.item.RecipeRegistry; +import org.geysermc.platform.spigot.GeyserSpigotPlugin; +import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.data.MappingData; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; +import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.Protocol1_13To1_12_2; + +import java.util.*; + +/** + * Used to send all available recipes from the server to the client, as a valid recipe book packet won't be sent by the server. + * Requires ViaVersion. + */ +public class GeyserSpigot1_11CraftingListener implements Listener { + + private final GeyserConnector connector; + /** + * Specific mapping data for 1.12 to 1.13. Used to convert the 1.12 item into 1.13. + */ + private final MappingData mappingData1_12to1_13; + /** + * The list of all protocols from the client's version to 1.13. + */ + private final List> protocolList; + private final ProtocolVersion version; + + public GeyserSpigot1_11CraftingListener(GeyserSpigotPlugin plugin, GeyserConnector connector) { + this.connector = connector; + this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData(); + this.protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, + ProtocolVersion.v1_13.getVersion()); + this.version = plugin.getServerProtocolVersion(); + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + GeyserSession session = null; + for (GeyserSession otherSession : connector.getPlayers()) { + if (otherSession.getName().equals(event.getPlayer().getName())) { + session = otherSession; + break; + } + } + if (session == null) { + return; + } + + System.out.println("Sending recipes!"); + sendServerRecipes(session); + } + + public void sendServerRecipes(GeyserSession session) { + int netId = RecipeRegistry.LAST_RECIPE_NET_ID; + + CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); + craftingDataPacket.setCleanRecipes(true); + Iterator recipeIterator = Bukkit.getServer().recipeIterator(); + while (recipeIterator.hasNext()) { + Recipe recipe = recipeIterator.next(); + Pair outputs = translateToBedrock(session, recipe.getResult()); + ItemStack javaOutput = outputs.getKey(); + ItemData output = outputs.getValue(); + if (output.getId() == 0) continue; // If items make air we don't want that + boolean isNotAllAir = false; // Check for all-air recipes + if (recipe instanceof ShapedRecipe) { + ShapedRecipe shapedRecipe = (ShapedRecipe) recipe; + int size = shapedRecipe.getShape().length * shapedRecipe.getShape()[0].length(); + Ingredient[] ingredients = new Ingredient[size]; + ItemData[] input = new ItemData[size]; + for (int i = 0; i < input.length; i++) { + // Index is converting char to integer, adding i then converting back to char based on ASCII code + Pair result = translateToBedrock(session, shapedRecipe.getIngredientMap().get((char) ('a' + i))); + ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()}); + input[i] = result.getValue(); + isNotAllAir = isNotAllAir || input[i].getId() != 0; + } + if (!isNotAllAir) continue; + UUID uuid = UUID.randomUUID(); + // Add recipe to our internal cache + ShapedRecipeData data = new ShapedRecipeData(shapedRecipe.getShape()[0].length(), shapedRecipe.getShape().length, + "", ingredients, javaOutput); + session.getCraftingRecipes().put(netId, + new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data)); + // Add recipe for Bedrock + craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(), + shapedRecipe.getShape()[0].length(), shapedRecipe.getShape().length, Arrays.asList(input), + Collections.singletonList(output), uuid, "crafting_table", 0, netId++)); + } else if (recipe instanceof ShapelessRecipe) { + ShapelessRecipe shapelessRecipe = (ShapelessRecipe) recipe; + Ingredient[] ingredients = new Ingredient[shapelessRecipe.getIngredientList().size()]; + ItemData[] input = new ItemData[shapelessRecipe.getIngredientList().size()]; + for (int i = 0; i < input.length; i++) { + Pair result = translateToBedrock(session, shapelessRecipe.getIngredientList().get(i)); + ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()}); + input[i] = result.getValue(); + isNotAllAir = isNotAllAir || input[i].getId() != 0; + } + if (!isNotAllAir) continue; + UUID uuid = UUID.randomUUID(); + // Add recipe to our internal cache + ShapelessRecipeData data = new ShapelessRecipeData("", ingredients, javaOutput); + session.getCraftingRecipes().put(netId, + new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPELESS, uuid.toString(), data)); + // Add recipe for Bedrock + craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), + Arrays.asList(input), Collections.singletonList(output), uuid, "crafting_table", 0, netId++)); + } + } + + session.sendUpstreamPacket(craftingDataPacket); + } + + @SuppressWarnings("deprecation") + private Pair translateToBedrock(GeyserSession session, org.bukkit.inventory.ItemStack itemStack) { + if (itemStack != null && itemStack.getData() != null) { + if (itemStack.getType().getId() == 0) { + return new Pair<>(null, ItemData.AIR); + } + int legacyId = (itemStack.getType().getId() << 4) | (itemStack.getData().getData() & 0xFFFF); + if (itemStack.getType().getId() == 355 && itemStack.getData().getData() == (byte) 0) { // Handle bed color since the server will always be pre-1.12 + legacyId = (itemStack.getType().getId() << 4) | ((byte) 14 & 0xFFFF); + } + // old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 and so on + int itemId; + if (mappingData1_12to1_13.getItemMappings().containsKey(legacyId)) { + itemId = mappingData1_12to1_13.getNewItemId(legacyId); + } else if (mappingData1_12to1_13.getItemMappings().containsKey((itemStack.getType().getId() << 4) | (0))) { + itemId = mappingData1_12to1_13.getNewItemId((itemStack.getType().getId() << 4) | (0)); + } else { + // No ID found, just send back air + return new Pair<>(null, ItemData.AIR); + } + + for (int i = protocolList.size() - 1; i >= 0; i--) { + MappingData mappingData = protocolList.get(i).getValue().getMappingData(); + if (mappingData != null) { + itemId = mappingData.getNewItemId(itemId); + } + } + + ItemStack mcItemStack = new ItemStack(itemId, itemStack.getAmount()); + ItemData finalData = ItemTranslator.translateToBedrock(session, mcItemStack); + return new Pair<>(mcItemStack, finalData); + } + // Empty slot, most likely + return new Pair<>(null, ItemData.AIR); + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java index 39a7db11d..bc6768a72 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java @@ -193,41 +193,60 @@ public class JavaSetSlotTranslator extends PacketTranslator } System.out.println("Sending packet!"); - ItemData[] ingredients = new ItemData[gridSize]; - //construct ingredient list and clear slots on client - for (int i = 0; i < gridSize; i++) { - ingredients[i] = inventory.getItem(i + 1).getItemData(session); + UUID uuid = UUID.randomUUID(); + int newRecipeId = session.getLastRecipeNetId().incrementAndGet(); - InventorySlotPacket slotPacket = new InventorySlotPacket(); - slotPacket.setContainerId(ContainerId.UI); - slotPacket.setSlot(i + offset); - slotPacket.setItem(ItemData.AIR); - session.sendUpstreamPacket(slotPacket); + ItemData[] ingredients = new ItemData[height * width]; + //construct ingredient list and clear slots on client + Ingredient[] javaIngredients = new Ingredient[height * width]; + int index = 0; + for (int row = firstRow; row < height + firstRow; row++) { + for (int col = firstCol; col < width + firstCol; col++) { + GeyserItemStack geyserItemStack = inventory.getItem(col + (row * gridDimensions) + 1); + ingredients[index] = geyserItemStack.getItemData(session); + ItemStack[] itemStacks = new ItemStack[] {geyserItemStack.isEmpty() ? null : geyserItemStack.getItemStack(1)}; + javaIngredients[index] = new Ingredient(itemStacks); + + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(col + (row * gridDimensions) + offset); + slotPacket.setItem(ItemData.AIR); + session.sendUpstreamPacket(slotPacket); + index++; + } } + ShapedRecipeData data = new ShapedRecipeData(width, height, "", javaIngredients, packet.getItem()); + session.getConnector().getLogger().error(data.toString()); + // Cache this recipe so we know the client has received it + session.getCraftingRecipes().put(newRecipeId, new Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data)); + CraftingDataPacket craftPacket = new CraftingDataPacket(); - UUID uuid = UUID.randomUUID(); craftPacket.getCraftingData().add(CraftingData.fromShaped( uuid.toString(), - gridDimensions, - gridDimensions, + width, + height, Arrays.asList(ingredients), Collections.singletonList(ItemTranslator.translateToBedrock(session, packet.getItem())), uuid, "crafting_table", 0, - session.getLastRecipeNetId().incrementAndGet() + newRecipeId )); craftPacket.setCleanRecipes(false); + System.out.println(craftPacket); session.sendUpstreamPacket(craftPacket); - //restore cleared slots - for (int i = 0; i < gridSize; i++) { - InventorySlotPacket slotPacket = new InventorySlotPacket(); - slotPacket.setContainerId(ContainerId.UI); - slotPacket.setSlot(i + offset); - slotPacket.setItem(ingredients[i]); - session.sendUpstreamPacket(slotPacket); + index = 0; + for (int row = firstRow; row < height + firstRow; row++) { + for (int col = firstCol; col < width + firstCol; col++) { + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.UI); + slotPacket.setSlot(col + (row * gridDimensions) + offset); + slotPacket.setItem(ingredients[index]); + session.sendUpstreamPacket(slotPacket); + index++; + } } } } From 6a9d4cd071211b18eb19040497292ad0f0094e67 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 11 Jan 2021 20:06:55 -0500 Subject: [PATCH 044/161] Implement 1.16.100 anvil renaming support --- .../translators/AnvilInventoryTranslator.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java index b131544b2..9d4fbfeec 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java @@ -26,20 +26,80 @@ package org.geysermc.connector.network.translators.inventory.translators; import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket; +import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; +import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import org.geysermc.connector.inventory.AnvilContainer; +import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.inventory.PlayerInventory; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; +import org.geysermc.connector.network.translators.item.ItemTranslator; public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { public AnvilInventoryTranslator() { super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, UIInventoryUpdater.INSTANCE); } + /* 1.16.100 support start */ + @Override + @Deprecated + public boolean shouldHandleRequestFirst(StackRequestActionData action, Inventory inventory) { + return action.getType() == StackRequestActionType.CRAFT_NON_IMPLEMENTED_DEPRECATED; + } + + @Override + @Deprecated + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + if (!(request.getActions()[1] instanceof CraftResultsDeprecatedStackRequestActionData)) { + // Just silently log an error + session.getConnector().getLogger().debug("Something isn't quite right with taking an item out of an anvil."); + return translateRequest(session, inventory, request); + } + CraftResultsDeprecatedStackRequestActionData actionData = (CraftResultsDeprecatedStackRequestActionData) request.getActions()[1]; + ItemData resultItem = actionData.getResultItems()[0]; + if (resultItem.getTag() != null) { + NbtMap displayTag = resultItem.getTag().getCompound("display"); + if (displayTag != null && displayTag.containsKey("Name")) { + ItemData sourceSlot = inventory.getItem(0).getItemData(session); + + if (sourceSlot.getTag() != null) { + NbtMap oldDisplayTag = sourceSlot.getTag().getCompound("display"); + if (oldDisplayTag != null && oldDisplayTag.containsKey("Name")) { + if (!displayTag.getString("Name").equals(oldDisplayTag.getString("Name"))) { + // Name has changed + sendRenamePacket(session, inventory, resultItem, displayTag.getString("Name")); + } + } else { + // No display tag on the old item + sendRenamePacket(session, inventory, resultItem, displayTag.getString("Name")); + } + } else { + // New NBT tag + sendRenamePacket(session, inventory, resultItem, displayTag.getString("Name")); + } + } + } + return translateRequest(session, inventory, request); + } + + private void sendRenamePacket(GeyserSession session, Inventory inventory, ItemData outputItem, String name) { + session.sendDownstreamPacket(new ClientRenameItemPacket(name)); + inventory.setItem(2, GeyserItemStack.from(ItemTranslator.translateToJava(outputItem)), session); + } + + /* 1.16.100 support end */ + @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { if (slotInfoData.getContainer() == ContainerSlotType.ANVIL_INPUT) { From d3cb0695946c05eb45dd911dfff7abd5266e0265 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Tue, 12 Jan 2021 16:37:12 -0900 Subject: [PATCH 045/161] WIP. Autocrafting without java recipe book --- .../inventory/InventoryTranslator.java | 336 +++++++++++++----- .../inventory/click/ClickPlan.java | 63 +++- 2 files changed, 305 insertions(+), 94 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index f0f734fcf..cb000a2b5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -27,12 +27,12 @@ package org.geysermc.connector.network.translators.inventory; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.data.game.recipe.Ingredient; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientPrepareCraftingGridPacket; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; @@ -40,8 +40,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.ints.*; import lombok.AllArgsConstructor; import org.geysermc.connector.inventory.CartographyContainer; import org.geysermc.connector.inventory.GeyserItemStack; @@ -159,8 +158,10 @@ public abstract class InventoryTranslator { if (shouldHandleRequestFirst(firstAction, inventory)) { // Some special request that shouldn't be processed normally responsePacket.getEntries().add(translateSpecialRequest(session, inventory, request)); - } else if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE || firstAction.getType() == StackRequestActionType.CRAFT_RECIPE_AUTO) { + } else if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE) { responsePacket.getEntries().add(translateCraftingRequest(session, inventory, request)); + } else if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE_AUTO) { + responsePacket.getEntries().add(translateAutoCraftingRequest(session, inventory, request)); } else if (firstAction.getType() == StackRequestActionType.CRAFT_CREATIVE) { // This is also used for pulling items out of creative responsePacket.getEntries().add(translateCreativeRequest(session, inventory, request)); @@ -329,40 +330,16 @@ public abstract class InventoryTranslator { } } else { //transfer from one slot to another int tempSlot = -1; - if (!cursor.isEmpty()) { + if (!plan.getCursor().isEmpty()) { tempSlot = findTempSlot(inventory, cursor, false, sourceSlot, destSlot); if (tempSlot == -1) { return rejectRequest(request); } plan.add(Click.LEFT, tempSlot); //place cursor into temp slot } - int sourceAmount = plan.getItem(sourceSlot).getAmount(); - if (transferAction.getCount() == sourceAmount) { //transfer all - plan.add(Click.LEFT, sourceSlot); //pickup source - plan.add(Click.LEFT, destSlot); //let go of all items and done - } else { //transfer some - //try to transfer items with least clicks possible - int halfSource = sourceAmount - (sourceAmount / 2); //larger half - int holding; - if (plan.getCursor().isEmpty() && transferAction.getCount() <= halfSource) { //faster to take only half. CURSOR MUST BE EMPTY - plan.add(Click.RIGHT, sourceSlot); - holding = halfSource; - } else { //need all - plan.add(Click.LEFT, sourceSlot); - holding = sourceAmount; - } - if (transferAction.getCount() > holding / 2) { //faster to release extra items onto source or dest slot? - for (int i = 0; i < holding - transferAction.getCount(); i++) { - plan.add(Click.RIGHT, sourceSlot); //prepare cursor - } - plan.add(Click.LEFT, destSlot); //release cursor onto dest slot - } else { - for (int i = 0; i < transferAction.getCount(); i++) { - plan.add(Click.RIGHT, destSlot); //right click until transfer goal is met - } - plan.add(Click.LEFT, sourceSlot); //return extra items to source slot - } - } + + transferSlot(plan, sourceSlot, destSlot, transferAction.getCount()); + if (tempSlot != -1) { plan.add(Click.LEFT, tempSlot); //retrieve original cursor } @@ -557,7 +534,6 @@ public abstract class InventoryTranslator { int recipeId = 0; int resultSize = 0; int timesCrafted = 0; - boolean autoCraft = false; CraftState craftState = CraftState.START; int leftover = 0; @@ -571,28 +547,6 @@ public abstract class InventoryTranslator { } craftState = CraftState.RECIPE_ID; recipeId = craftAction.getRecipeNetworkId(); - autoCraft = false; - break; - } - case CRAFT_RECIPE_AUTO: { - AutoCraftRecipeStackRequestActionData autoCraftAction = (AutoCraftRecipeStackRequestActionData) action; - if (craftState != CraftState.START) { - return rejectRequest(request); - } - craftState = CraftState.RECIPE_ID; - - recipeId = autoCraftAction.getRecipeNetworkId(); - if (!plan.getCursor().isEmpty()) { - return rejectRequest(request); - } - //reject if crafting grid is not clear - int gridSize = inventory.getId() == 0 ? 4 : 9; - for (int i = 1; i <= gridSize; i++) { - if (!inventory.getItem(i).isEmpty()) { - return rejectRequest(request); - } - } - autoCraft = true; break; } case CRAFT_RESULTS_DEPRECATED: { @@ -638,39 +592,6 @@ public abstract class InventoryTranslator { int sourceSlot = bedrockSlotToJava(transferAction.getSource()); int destSlot = bedrockSlotToJava(transferAction.getDestination()); - if (autoCraft) { - Recipe recipe = session.getCraftingRecipes().get(recipeId); - //cannot use java recipe book if recipe is locked - if (recipe == null || !session.getUnlockedRecipes().contains(recipe.getIdentifier())) { - return rejectRequest(request); - } - - boolean cursorDest = isCursor(transferAction.getDestination()); - boolean makeAll = timesCrafted > 1; - if (cursorDest) { - makeAll = false; - } - - ClientPrepareCraftingGridPacket prepareCraftingPacket = new ClientPrepareCraftingGridPacket(inventory.getId(), recipe.getIdentifier(), makeAll); - session.sendDownstreamPacket(prepareCraftingPacket); - - ItemStack output = null; - switch (recipe.getType()) { - case CRAFTING_SHAPED: - output = ((ShapedRecipeData)recipe.getData()).getResult(); - break; - case CRAFTING_SHAPELESS: - output = ((ShapelessRecipeData)recipe.getData()).getResult(); - break; - } - inventory.setItem(0, GeyserItemStack.from(output), session); - - plan.add(cursorDest ? Click.LEFT : Click.LEFT_SHIFT, 0); - plan.execute(true); - - return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); - } - if (isCursor(transferAction.getDestination())) { plan.add(Click.LEFT, sourceSlot); craftState = CraftState.DONE; @@ -711,9 +632,211 @@ public abstract class InventoryTranslator { } } plan.execute(false); - Set affectedSlots = plan.getAffectedSlots(); - affectedSlots.addAll(Arrays.asList(1, 2, 3, 4)); //TODO: crafting grid - return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); + return acceptRequest(request, makeContainerEntries(session, inventory, plan.getAffectedSlots())); + } + + public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + int gridSize; + int gridDimensions; + if (this instanceof PlayerInventoryTranslator) { + gridSize = 4; + gridDimensions = 2; + } else if (this instanceof CraftingInventoryTranslator) { + gridSize = 9; + gridDimensions = 3; + } else { + return rejectRequest(request); + } + + Recipe recipe; + Ingredient[] ingredients = new Ingredient[0]; + ItemStack output = null; + int recipeWidth = 0; + int ingRemaining = 0; + int ingredientIndex = -1; + + Int2IntMap consumedSlots = new Int2IntOpenHashMap(); + int prioritySlot = -1; + int secondarySlot = -1; + int tempSlot = -1; + boolean intoCursor = false; + + int resultSize; + int timesCrafted = 0; + Int2ObjectMap ingredientMap = new Int2ObjectOpenHashMap<>(); + CraftState craftState = CraftState.START; + + ClickPlan plan = new ClickPlan(session, this, inventory); + requestLoop: + for (StackRequestActionData action : request.getActions()) { + switch (action.getType()) { + case CRAFT_RECIPE_AUTO: { + AutoCraftRecipeStackRequestActionData autoCraftAction = (AutoCraftRecipeStackRequestActionData) action; + if (craftState != CraftState.START) { + return rejectRequest(request); + } + craftState = CraftState.RECIPE_ID; + + int recipeId = autoCraftAction.getRecipeNetworkId(); + recipe = session.getCraftingRecipes().get(recipeId); + if (recipe == null) { + return rejectRequest(request); + } + if (!plan.getCursor().isEmpty()) { + return rejectRequest(request); + } + //reject if crafting grid is not clear + for (int i = 1; i <= gridSize; i++) { + if (!inventory.getItem(i).isEmpty()) { + return rejectRequest(request); + } + } + + switch (recipe.getType()) { + case CRAFTING_SHAPED: + ShapedRecipeData shapedData = (ShapedRecipeData) recipe.getData(); + ingredients = shapedData.getIngredients(); + recipeWidth = shapedData.getWidth(); + output = shapedData.getResult(); + if (shapedData.getWidth() > gridDimensions || shapedData.getHeight() > gridDimensions) { + return rejectRequest(request); + } + break; + case CRAFTING_SHAPELESS: + ShapelessRecipeData shapelessData = (ShapelessRecipeData) recipe.getData(); + ingredients = shapelessData.getIngredients(); + recipeWidth = gridDimensions; + output = shapelessData.getResult(); + if (ingredients.length > gridSize) { + return rejectRequest(request); + } + break; + } + break; + } + case CRAFT_RESULTS_DEPRECATED: { + CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action; + if (craftState != CraftState.RECIPE_ID) { + return rejectRequest(request); + } + craftState = CraftState.DEPRECATED; + + if (deprecatedCraftAction.getResultItems().length != 1) { + return rejectRequest(request); + } + resultSize = deprecatedCraftAction.getResultItems()[0].getCount(); + timesCrafted = deprecatedCraftAction.getTimesCrafted(); + if (resultSize <= 0 || timesCrafted <= 0) { + return rejectRequest(request); + } + break; + } + case CONSUME: { + ConsumeStackRequestActionData consumeAction = (ConsumeStackRequestActionData) action; + if (craftState != CraftState.DEPRECATED && craftState != CraftState.INGREDIENTS) { + return rejectRequest(request); + } + craftState = CraftState.INGREDIENTS; + + if (ingRemaining == 0) { + while (++ingredientIndex < ingredients.length) { + if (ingredients[ingredientIndex].getOptions().length != 0) { + ingRemaining = timesCrafted; + break; + } + } + } + + ingRemaining -= consumeAction.getCount(); + if (ingRemaining < 0) + return rejectRequest(request); + + int javaSlot = bedrockSlotToJava(consumeAction.getSource()); + consumedSlots.merge(javaSlot, consumeAction.getCount(), Integer::sum); + + int gridSlot = 1 + ingredientIndex + ((ingredientIndex / recipeWidth) * (gridDimensions - recipeWidth)); + Int2IntMap sources = ingredientMap.computeIfAbsent(gridSlot, k -> new Int2IntOpenHashMap()); + sources.put(javaSlot, consumeAction.getCount()); + break; + } + case TAKE: + case PLACE: { + TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; + if (craftState != CraftState.INGREDIENTS && craftState != CraftState.TRANSFER) { + return rejectRequest(request); + } + craftState = CraftState.TRANSFER; + + if (transferAction.getSource().getContainer() != ContainerSlotType.CREATIVE_OUTPUT) { + return rejectRequest(request); + } + if (transferAction.getCount() <= 0) { + return rejectRequest(request); + } + + int javaSlot = bedrockSlotToJava(transferAction.getDestination()); + if (isCursor(transferAction.getDestination())) { //TODO + intoCursor = true; + if (timesCrafted > 1) { + tempSlot = findTempSlot(inventory, GeyserItemStack.from(output), true); + if (tempSlot == -1) { + return rejectRequest(request); + } + } + break requestLoop; + } else if (inventory.getItem(javaSlot).getAmount() == consumedSlots.get(javaSlot)) { + prioritySlot = bedrockSlotToJava(transferAction.getDestination()); + break requestLoop; + } + break; + } + default: + return rejectRequest(request); + } + } + + final int maxLoops = Math.min(64, timesCrafted); + for (int loops = 0; loops < maxLoops; loops++) { + boolean done = true; + for (Int2ObjectMap.Entry entry : ingredientMap.int2ObjectEntrySet()) { + Int2IntMap sources = entry.getValue(); + System.out.println("Grid slot: " + entry.getIntKey()); + System.out.println(sources); + if (sources.isEmpty()) + continue; + + done = false; + int gridSlot = entry.getIntKey(); + if (!plan.getItem(gridSlot).isEmpty()) + continue; + + int sourceSlot; + if (loops == 0 && sources.containsKey(prioritySlot)) { + sourceSlot = prioritySlot; + } else { + sourceSlot = sources.keySet().iterator().nextInt(); + } + int transferAmount = sources.remove(sourceSlot); + transferSlot(plan, sourceSlot, gridSlot, transferAmount); + } + + for (int x = 0; x < 3; x++) { + int offset = x * 3; + System.out.println(plan.getItem(1 + offset).getAmount() + " " + plan.getItem(2 + offset).getAmount() + " " + plan.getItem(3 + offset).getAmount()); + } + + if (!done) { + //TODO: sometimes the server does not agree on this slot? + plan.add(Click.LEFT_SHIFT, 0, true); + } else { + System.out.println("Times looped: " + loops); + break; + } + } + + inventory.setItem(0, GeyserItemStack.from(output), session); + plan.execute(true); + return acceptRequest(request, makeContainerEntries(session, inventory, plan.getAffectedSlots())); } public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { @@ -788,6 +911,37 @@ public abstract class InventoryTranslator { return rejectRequest(request); } + private void transferSlot(ClickPlan plan, int sourceSlot, int destSlot, int transferAmount) { + boolean tempSwap = !plan.getCursor().isEmpty(); + int sourceAmount = plan.getItem(sourceSlot).getAmount(); + if (transferAmount == sourceAmount) { //transfer all + plan.add(Click.LEFT, sourceSlot); //pickup source + plan.add(Click.LEFT, destSlot); //let go of all items and done + } else { //transfer some + //try to transfer items with least clicks possible + int halfSource = sourceAmount - (sourceAmount / 2); //larger half + int holding; + if (!tempSwap && transferAmount <= halfSource) { //faster to take only half. CURSOR MUST BE EMPTY + plan.add(Click.RIGHT, sourceSlot); + holding = halfSource; + } else { //need all + plan.add(Click.LEFT, sourceSlot); + holding = sourceAmount; + } + if (!tempSwap && transferAmount > holding / 2) { //faster to release extra items onto source or dest slot? + for (int i = 0; i < holding - transferAmount; i++) { + plan.add(Click.RIGHT, sourceSlot); //prepare cursor + } + plan.add(Click.LEFT, destSlot); //release cursor onto dest slot + } else { + for (int i = 0; i < transferAmount; i++) { + plan.add(Click.RIGHT, destSlot); //right click until transfer goal is met + } + plan.add(Click.LEFT, sourceSlot); //return extra items to source slot + } + } + } + public static ItemStackResponsePacket.Response acceptRequest(ItemStackRequestPacket.Request request, List containerEntries) { return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.OK, request.getRequestId(), containerEntries); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java index 9268cbf12..018f1bc78 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java @@ -39,6 +39,8 @@ import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.inventory.SlotType; +import org.geysermc.connector.network.translators.inventory.translators.CraftingInventoryTranslator; +import org.geysermc.connector.network.translators.inventory.translators.PlayerInventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; import java.util.*; @@ -52,6 +54,7 @@ public class ClickPlan { private final GeyserSession session; private final InventoryTranslator translator; private final Inventory inventory; + private final int gridSize; public ClickPlan(GeyserSession session, InventoryTranslator translator, Inventory inventory) { this.session = session; @@ -61,6 +64,14 @@ public class ClickPlan { this.simulatedItems = new Int2ObjectOpenHashMap<>(inventory.getSize()); this.simulatedCursor = session.getPlayerInventory().getCursor().copy(); this.simulating = true; + + if (translator instanceof PlayerInventoryTranslator) { + gridSize = 4; + } else if (translator instanceof CraftingInventoryTranslator) { + gridSize = 9; + } else { + gridSize = -1; + } } private void resetSimulation() { @@ -69,6 +80,10 @@ public class ClickPlan { } public void add(Click click, int slot) { + add(click, slot, false); + } + + public void add(Click click, int slot, boolean force) { if (!simulating) throw new UnsupportedOperationException("ClickPlan already executed"); @@ -76,7 +91,7 @@ public class ClickPlan { slot = Click.OUTSIDE_SLOT; } - ClickAction action = new ClickAction(click, slot); + ClickAction action = new ClickAction(click, slot, force); plan.add(action); simulateAction(action); } @@ -114,7 +129,7 @@ public class ClickPlan { simulateAction(action); session.sendDownstreamPacket(clickPacket); - if (clickedItemStack == InventoryUtils.REFRESH_ITEM) { + if (clickedItemStack == InventoryUtils.REFRESH_ITEM || action.force) { session.sendDownstreamPacket(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true)); } System.out.println(clickPacket); @@ -128,7 +143,15 @@ public class ClickPlan { } public GeyserItemStack getItem(int slot) { - return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy()); + return getItem(slot, true); + } + + public GeyserItemStack getItem(int slot, boolean generate) { + if (generate) { + return simulatedItems.computeIfAbsent(slot, k -> inventory.getItem(slot).copy()); + } else { + return simulatedItems.getOrDefault(slot, inventory.getItem(slot)); + } } public GeyserItemStack getCursor() { @@ -174,6 +197,10 @@ public class ClickPlan { } else if (InventoryUtils.canStack(cursor, clicked)) { cursor.add(clicked.getAmount()); } + reduceCraftingGrid(false); + break; + case LEFT_SHIFT: + reduceCraftingGrid(true); break; } } else { @@ -215,6 +242,35 @@ public class ClickPlan { } } + //TODO + private void reduceCraftingGrid(boolean makeAll) { + if (gridSize == -1) + return; + + int crafted; + if (!makeAll) { + crafted = 1; + } else { + crafted = 0; + for (int i = 0; i < gridSize; i++) { + GeyserItemStack item = getItem(i + 1); + if (!item.isEmpty()) { + if (crafted == 0) { + crafted = item.getAmount(); + } + crafted = Math.min(crafted, item.getAmount()); + } + } + } + + for (int i = 0; i < gridSize; i++) { + GeyserItemStack item = getItem(i + 1); + if (!item.isEmpty()) + item.sub(crafted); + } + System.out.println("REDUCED GRID BY " + crafted); + } + /** * @return a new set of all affected slots. This isn't a constant variable; it's newly generated each time it is run. */ @@ -235,5 +291,6 @@ public class ClickPlan { * Java slot */ int slot; + boolean force; } } From 454fd102d6aa8334ce76768ba35a63d8cccf47d4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 13 Jan 2021 13:37:20 -0500 Subject: [PATCH 046/161] Add better destroy support --- .../inventory/InventoryTranslator.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index cb000a2b5..f029a6051 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -465,7 +465,6 @@ public abstract class InventoryTranslator { } case DESTROY: { // Only called when a creative client wants to destroy an item... I think - Camotoy - //TODO there is a Count here we don't use DestroyStackRequestActionData destroyAction = (DestroyStackRequestActionData) action; if (!session.getGameMode().equals(GameMode.CREATIVE)) { // If this happens, let's throw an error and figure out why. @@ -474,13 +473,23 @@ public abstract class InventoryTranslator { if (!isCursor(destroyAction.getSource())) { // Item exists; let's remove it from the inventory int javaSlot = bedrockSlotToJava(destroyAction.getSource()); - ClientCreativeInventoryActionPacket destroyItemPacket = new ClientCreativeInventoryActionPacket( - javaSlot, - new ItemStack(0) - ); + GeyserItemStack existingItem = inventory.getItem(javaSlot); + existingItem.setAmount(existingItem.getAmount() - destroyAction.getCount()); + ClientCreativeInventoryActionPacket destroyItemPacket; + if (existingItem.isEmpty()) { + destroyItemPacket = new ClientCreativeInventoryActionPacket( + javaSlot, + new ItemStack(0) + ); + inventory.setItem(javaSlot, GeyserItemStack.EMPTY, session); + } else { + destroyItemPacket = new ClientCreativeInventoryActionPacket( + javaSlot, + existingItem.getItemStack() + ); + } session.sendDownstreamPacket(destroyItemPacket); System.out.println(destroyItemPacket); - inventory.setItem(javaSlot, GeyserItemStack.EMPTY, session); affectedSlots.add(javaSlot); } else { // Just sync up the item on our end, since the server doesn't care what's in our cursor From 196c9f5c17a2d78af5e5fcc57f905217d5480991 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Wed, 13 Jan 2021 19:19:01 -0900 Subject: [PATCH 047/161] Remove debug lines --- .../GeyserSpigot1_11CraftingListener.java | 1 - .../network/session/GeyserSession.java | 2 -- .../BedrockContainerCloseTranslator.java | 1 - .../bedrock/BedrockFilterTextTranslator.java | 1 - ...BedrockInventoryTransactionTranslator.java | 1 - .../BedrockItemStackRequestTranslator.java | 1 - .../BedrockLecternUpdateTranslator.java | 4 --- .../inventory/InventoryTranslator.java | 25 ------------------- .../inventory/click/ClickPlan.java | 2 -- .../BeaconInventoryTranslator.java | 2 -- .../BrewingInventoryTranslator.java | 1 - .../EnchantingInventoryTranslator.java | 2 -- .../translators/LoomInventoryTranslator.java | 1 - .../StonecutterInventoryTranslator.java | 3 --- .../ChestedHorseInventoryTranslator.java | 1 - .../updater/HorseInventoryUpdater.java | 1 - .../JavaConfirmTransactionTranslator.java | 1 - .../window/JavaOpenHorseWindowTranslator.java | 2 -- .../java/window/JavaSetSlotTranslator.java | 10 -------- .../window/JavaWindowPropertyTranslator.java | 1 - 20 files changed, 63 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java index d20b7637d..66fbb0163 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java @@ -93,7 +93,6 @@ public class GeyserSpigot1_11CraftingListener implements Listener { return; } - System.out.println("Sending recipes!"); sendServerRecipes(session); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 4542abf77..50ab17e23 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -887,7 +887,6 @@ public class GeyserSession implements CommandSender { */ public void addInventoryTask(Runnable task) { synchronized (inventoryLock) { - System.out.println("new task " + task.toString()); inventoryFuture = inventoryFuture.thenRun(task).exceptionally(throwable -> { GeyserConnector.getInstance().getLogger().error("Error processing inventory task", throwable.getCause()); return null; @@ -905,7 +904,6 @@ public class GeyserSession implements CommandSender { */ public void addInventoryTask(Runnable task, long delayMillis) { synchronized (inventoryLock) { - System.out.println("new delayed task " + task.toString()); Executor delayedExecutor = command -> GeyserConnector.getInstance().getGeneralThreadPool().schedule(command, delayMillis, TimeUnit.MILLISECONDS); inventoryFuture = inventoryFuture.thenRunAsync(task, delayedExecutor).exceptionally(throwable -> { GeyserConnector.getInstance().getLogger().error("Error processing inventory task", throwable.getCause()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java index 7ac3dba51..6f0f877c8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java @@ -48,7 +48,6 @@ public class BedrockContainerCloseTranslator extends PacketTranslator currentJavaPage) { for (int i = currentJavaPage; i < newJavaPage; i++) { ClientClickWindowButtonPacket clickButtonPacket = new ClientClickWindowButtonPacket(session.getOpenInventory().getId(), 2); - System.out.println(clickButtonPacket); session.sendDownstreamPacket(clickButtonPacket); } } else { for (int i = currentJavaPage; i > newJavaPage; i--) { ClientClickWindowButtonPacket clickButtonPacket = new ClientClickWindowButtonPacket(session.getOpenInventory().getId(), 1); - System.out.println(clickButtonPacket); session.sendDownstreamPacket(clickButtonPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 654a7b422..fab81933a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -173,11 +173,9 @@ public abstract class InventoryTranslator { } } session.sendUpstreamPacket(responsePacket); - System.out.println(responsePacket); } public ItemStackResponsePacket.Response translateRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { - System.out.println(request); ClickPlan plan = new ClickPlan(session, this, inventory); IntSet affectedSlots = new IntOpenHashSet(); for (StackRequestActionData action : request.getActions()) { @@ -273,7 +271,6 @@ public abstract class InventoryTranslator { itemToUpdate.isEmpty() ? new ItemStack(0) : itemToUpdate.getItemStack() ); session.sendDownstreamPacket(creativeActionPacket); - System.out.println(creativeActionPacket); if (!sourceIsCursor) { // Cursor is always added for us as an affected slot affectedSlots.add(sourceSlot); @@ -365,7 +362,6 @@ public abstract class InventoryTranslator { sourceSlot, oldDestinationItem.isEmpty() ? new ItemStack(0) : oldDestinationItem.getItemStack() // isEmpty check... just in case ); - System.out.println(creativeActionPacket); session.sendDownstreamPacket(creativeActionPacket); inventory.setItem(sourceSlot, oldDestinationItem, session); } @@ -376,7 +372,6 @@ public abstract class InventoryTranslator { destSlot, oldSourceItem.isEmpty() ? new ItemStack(0) : oldSourceItem.getItemStack() ); - System.out.println(creativeActionPacket); session.sendDownstreamPacket(creativeActionPacket); inventory.setItem(destSlot, oldSourceItem, session); } @@ -433,7 +428,6 @@ public abstract class InventoryTranslator { Click.OUTSIDE_SLOT, droppingItem.getItemStack() ); - System.out.println(packet.toString()); session.sendDownstreamPacket(packet); } else { int sourceAmount = plan.getCursor().getAmount(); @@ -458,11 +452,6 @@ public abstract class InventoryTranslator { } break; } - case CRAFT_CREATIVE: { - CraftCreativeStackRequestActionData creativeAction = (CraftCreativeStackRequestActionData) action; - System.out.println(creativeAction.getCreativeItemNetworkId()); - break; - } case DESTROY: { // Only called when a creative client wants to destroy an item... I think - Camotoy DestroyStackRequestActionData destroyAction = (DestroyStackRequestActionData) action; @@ -489,7 +478,6 @@ public abstract class InventoryTranslator { ); } session.sendDownstreamPacket(destroyItemPacket); - System.out.println(destroyItemPacket); affectedSlots.add(javaSlot); } else { // Just sync up the item on our end, since the server doesn't care what's in our cursor @@ -538,8 +526,6 @@ public abstract class InventoryTranslator { } public ItemStackResponsePacket.Response translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { - System.out.println(request); - int recipeId = 0; int resultSize = 0; int timesCrafted = 0; @@ -809,8 +795,6 @@ public abstract class InventoryTranslator { boolean done = true; for (Int2ObjectMap.Entry entry : ingredientMap.int2ObjectEntrySet()) { Int2IntMap sources = entry.getValue(); - System.out.println("Grid slot: " + entry.getIntKey()); - System.out.println(sources); if (sources.isEmpty()) continue; @@ -829,16 +813,10 @@ public abstract class InventoryTranslator { transferSlot(plan, sourceSlot, gridSlot, transferAmount); } - for (int x = 0; x < 3; x++) { - int offset = x * 3; - System.out.println(plan.getItem(1 + offset).getAmount() + " " + plan.getItem(2 + offset).getAmount() + " " + plan.getItem(3 + offset).getAmount()); - } - if (!done) { //TODO: sometimes the server does not agree on this slot? plan.add(Click.LEFT_SHIFT, 0, true); } else { - System.out.println("Times looped: " + loops); break; } } @@ -908,7 +886,6 @@ public abstract class InventoryTranslator { javaCreativeItem ); session.sendDownstreamPacket(creativeActionPacket); - System.out.println(creativeActionPacket); Set affectedSlots = Collections.singleton(javaSlot); return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); } @@ -1017,8 +994,6 @@ public abstract class InventoryTranslator { if (!viable) { continue; } - - System.out.println("TEMP SLOT CHOSEN: " + i + " => " + inventory.getItem(i)); return i; } //could not find a viable temp slot diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java index 018f1bc78..c750baf51 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/click/ClickPlan.java @@ -132,7 +132,6 @@ public class ClickPlan { if (clickedItemStack == InventoryUtils.REFRESH_ITEM || action.force) { session.sendDownstreamPacket(new ClientConfirmTransactionPacket(inventory.getId(), actionId, true)); } - System.out.println(clickPacket); } session.getPlayerInventory().setCursor(simulatedCursor, session); @@ -268,7 +267,6 @@ public class ClickPlan { if (!item.isEmpty()) item.sub(crafted); } - System.out.println("REDUCED GRID BY " + crafted); } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java index 5b31c83ba..d0db8f3d6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java @@ -84,7 +84,6 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator BlockEntityDataPacket packet = new BlockEntityDataPacket(); packet.setBlockPosition(position); packet.setData(builder.build()); - System.out.println(packet.toString()); session.sendUpstreamPacket(packet); } @@ -98,7 +97,6 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator // Input a beacon payment BeaconPaymentStackRequestActionData beaconPayment = (BeaconPaymentStackRequestActionData) request.getActions()[0]; ClientSetBeaconEffectPacket packet = new ClientSetBeaconEffectPacket(beaconPayment.getPrimaryEffect(), beaconPayment.getSecondaryEffect()); - System.out.println(packet.toString()); session.sendDownstreamPacket(packet); return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java index 9dbbbf964..b44350615 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java @@ -69,7 +69,6 @@ public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { - System.out.println("Brewing stand: " + slotInfoData); if (slotInfoData.getContainer() == ContainerSlotType.BREWING_INPUT) { // Ingredient // TODO: This hasn't worked and then suddenly, it did. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java index 4d105d423..8d2f20145 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java @@ -99,7 +99,6 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla enchantingInventory.getEnchantOptions()[slotToUpdate] = enchantingInventory.getGeyserEnchantOptions()[slotToUpdate].build(session); PlayerEnchantOptionsPacket packet = new PlayerEnchantOptionsPacket(); packet.getOptions().addAll(Arrays.asList(enchantingInventory.getEnchantOptions())); - System.out.println(packet); session.sendUpstreamPacket(packet); } } @@ -130,7 +129,6 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla return rejectRequest(request); } ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), javaSlot); - System.out.println(packet); session.sendDownstreamPacket(packet); return acceptRequest(request, makeContainerEntries(session, inventory, Collections.emptySet())); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java index bac9e7ae6..409cc4f15 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java @@ -140,7 +140,6 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { // And the Java loom window has a fixed row/width of four // So... Number / 4 = row (so we don't have to bother there), and number % 4 is our column, which leads us back to our index. :) ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), index); - System.out.println(packet); session.sendDownstreamPacket(packet); GeyserItemStack inputCopy = inventory.getItem(0).copy(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java index 0168d0ef9..af742c136 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java @@ -73,15 +73,12 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl if (results == null) { return rejectRequest(request); } - System.out.println(id + " " + results); ItemStack javaOutput = ItemTranslator.translateToJava(craftData.getResultItems()[0]); - System.out.println(javaOutput); int button = results.indexOf(javaOutput.getId()); // If we've already pressed the button with this item, no need to press it again! if (container.getStonecutterButton() != button) { // Getting the index of the item in the Java stonecutter list ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), button); - System.out.println(packet.toString()); session.sendDownstreamPacket(packet); container.setStonecutterButton(button); if (inventory.getItem(1).getJavaId() != javaOutput.getId()) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java index f74c2d361..77a1976be 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/ChestedHorseInventoryTranslator.java @@ -107,7 +107,6 @@ public abstract class ChestedHorseInventoryTranslator extends AbstractHorseInven InventoryContentPacket horseContentsPacket = new InventoryContentPacket(); horseContentsPacket.setContainerId(inventory.getId()); horseContentsPacket.setContents(Arrays.asList(horseItems)); - System.out.println(horseContentsPacket); session.sendUpstreamPacket(horseContentsPacket); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java index 838e59d78..d238b4148 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java @@ -62,7 +62,6 @@ public class HorseInventoryUpdater extends InventoryUpdater { slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); slotPacket.setItem(inventory.getItem(javaSlot).getItemData(session)); session.sendUpstreamPacket(slotPacket); - System.out.println(slotPacket); return true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java index 05347fd06..3b55733bf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.java @@ -41,7 +41,6 @@ public class JavaConfirmTransactionTranslator extends PacketTranslator @Override public void translate(ServerSetSlotPacket packet, GeyserSession session) { - System.out.println(packet.toString()); session.addInventoryTask(() -> { if (packet.getWindowId() == 255) { //cursor GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); @@ -125,11 +124,6 @@ public class JavaSetSlotTranslator extends PacketTranslator height += -firstRow + 1; width += -firstCol + 1; - System.out.println("Start Row: " + firstRow); - System.out.println("Start Column: " + firstCol); - System.out.println("Rows: " + height); - System.out.println("Columns: " + width); - //TODO recipes: for (Recipe recipe : session.getCraftingRecipes().values()) { @@ -157,8 +151,6 @@ public class JavaSetSlotTranslator extends PacketTranslator continue; } } - System.out.println("FOUND SHAPED RECIPE :)"); - System.out.println(recipe); // Recipe is had, don't sent packet return; } else if (recipe.getType() == RecipeType.CRAFTING_SHAPELESS) { @@ -191,7 +183,6 @@ public class JavaSetSlotTranslator extends PacketTranslator return; } } - System.out.println("Sending packet!"); UUID uuid = UUID.randomUUID(); int newRecipeId = session.getLastRecipeNetId().incrementAndGet(); @@ -234,7 +225,6 @@ public class JavaSetSlotTranslator extends PacketTranslator newRecipeId )); craftPacket.setCleanRecipes(false); - System.out.println(craftPacket); session.sendUpstreamPacket(craftPacket); index = 0; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java index 94ec8784d..c31a39029 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java @@ -38,7 +38,6 @@ public class JavaWindowPropertyTranslator extends PacketTranslator { Inventory inventory = InventoryUtils.getInventory(session, packet.getWindowId()); if (inventory == null) From 5ddb189b4881c246d229ddc37fbeec9e1cac70f5 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Wed, 13 Jan 2021 19:19:37 -0900 Subject: [PATCH 048/161] Use server-inventory mappings --- connector/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index dd0347bd5..07f65c380 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit dd0347bd51e00e42ea58faaf68b562526c4d2817 +Subproject commit 07f65c3803dcd3f83358ee574e54bf129cad0840 From 4ee3143fe931c54ea248ffbf2554602cf753371a Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Wed, 13 Jan 2021 19:40:01 -0900 Subject: [PATCH 049/161] Add delay to legacy recipe detection Workaround for an issue which will be properly fixed later. --- .../geysermc/connector/network/session/GeyserSession.java | 3 +++ .../translators/java/window/JavaSetSlotTranslator.java | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 50ab17e23..17c6bd66c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -151,6 +151,9 @@ public class GeyserSession implements CommandSender { @Getter(AccessLevel.NONE) private CompletableFuture inventoryFuture; + @Setter + private ScheduledFuture craftingGridFuture; + /** * Stores session collision */ diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java index ea2c9d555..c5bf6dd25 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java @@ -52,6 +52,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Objects; import java.util.UUID; +import java.util.concurrent.TimeUnit; @Translator(packet = ServerSetSlotPacket.class) public class JavaSetSlotTranslator extends PacketTranslator { @@ -73,7 +74,10 @@ public class JavaSetSlotTranslator extends PacketTranslator InventoryTranslator translator = session.getInventoryTranslator(); if (translator != null) { - updateCraftingGrid(session, packet, inventory, translator); + if (session.getCraftingGridFuture() != null) { + session.getCraftingGridFuture().cancel(false); + } + session.setCraftingGridFuture(session.getConnector().getGeneralThreadPool().schedule(() -> session.addInventoryTask(() -> updateCraftingGrid(session, packet, inventory, translator)), 150, TimeUnit.MILLISECONDS)); GeyserItemStack newItem = GeyserItemStack.from(packet.getItem()); inventory.setItem(packet.getSlot(), newItem, session); @@ -208,7 +212,6 @@ public class JavaSetSlotTranslator extends PacketTranslator } ShapedRecipeData data = new ShapedRecipeData(width, height, "", javaIngredients, packet.getItem()); - session.getConnector().getLogger().error(data.toString()); // Cache this recipe so we know the client has received it session.getCraftingRecipes().put(newRecipeId, new Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data)); From 95703d2ea7794da4e747af5cebaaa650de5d3438 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Wed, 13 Jan 2021 19:59:11 -0900 Subject: [PATCH 050/161] Update inventory if request was rejected --- .../inventory/InventoryTranslator.java | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index fab81933a..64f73ac54 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -151,28 +151,39 @@ public abstract class InventoryTranslator { } public void translateRequests(GeyserSession session, Inventory inventory, List requests) { + boolean refresh = false; ItemStackResponsePacket responsePacket = new ItemStackResponsePacket(); for (ItemStackRequestPacket.Request request : requests) { + ItemStackResponsePacket.Response response; if (request.getActions().length > 0) { StackRequestActionData firstAction = request.getActions()[0]; if (shouldHandleRequestFirst(firstAction, inventory)) { // Some special request that shouldn't be processed normally - responsePacket.getEntries().add(translateSpecialRequest(session, inventory, request)); + response = translateSpecialRequest(session, inventory, request); } else if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE) { - responsePacket.getEntries().add(translateCraftingRequest(session, inventory, request)); + response = translateCraftingRequest(session, inventory, request); } else if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE_AUTO) { - responsePacket.getEntries().add(translateAutoCraftingRequest(session, inventory, request)); + response = translateAutoCraftingRequest(session, inventory, request); } else if (firstAction.getType() == StackRequestActionType.CRAFT_CREATIVE) { // This is also used for pulling items out of creative - responsePacket.getEntries().add(translateCreativeRequest(session, inventory, request)); + response = translateCreativeRequest(session, inventory, request); } else { - responsePacket.getEntries().add(translateRequest(session, inventory, request)); + response = translateRequest(session, inventory, request); } } else { - responsePacket.getEntries().add(rejectRequest(request)); + response = rejectRequest(request); } + if (response.getResult() == ItemStackResponsePacket.ResponseStatus.ERROR) { + refresh = true; + } + responsePacket.getEntries().add(response); } session.sendUpstreamPacket(responsePacket); + + if (refresh) { + InventoryUtils.updateCursor(session); + updateInventory(session, inventory); + } } public ItemStackResponsePacket.Response translateRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { From 234c6542bbabf5a1314e883208af1e595c9e2e42 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Thu, 14 Jan 2021 19:45:59 -0900 Subject: [PATCH 051/161] Refactor creative request translator and fix bugs --- .../inventory/InventoryTranslator.java | 237 +--------------- .../PlayerInventoryTranslator.java | 255 ++++++++++++++++++ 2 files changed, 265 insertions(+), 227 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 64f73ac54..b6f22dc61 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -217,79 +217,6 @@ public abstract class InventoryTranslator { if (isCursor(transferAction.getSource()) && isCursor(transferAction.getDestination())) { //??? return rejectRequest(request); - } else if (session.getGameMode().equals(GameMode.CREATIVE) && inventory instanceof PlayerInventory) { // TODO: does the Java server use this stuff all the time in creative? - // Creative acts a little differently because it just edits slots - boolean sourceIsCursor = isCursor(transferAction.getSource()); - boolean destIsCursor = isCursor(transferAction.getDestination()); - - GeyserItemStack sourceItem = sourceIsCursor ? session.getPlayerInventory().getCursor() : - inventory.getItem(sourceSlot); - GeyserItemStack newItem = sourceItem.copy(); - if (sourceIsCursor) { - GeyserItemStack destItem = inventory.getItem(destSlot); - if (destItem.getJavaId() == sourceItem.getJavaId()) { - // Combining items - int itemsLeftOver = destItem.getAmount() + transferAction.getCount(); - if (itemsLeftOver > MAX_ITEM_STACK_SIZE) { - // Items will remain in cursor because destination slot gets set to 64 - destItem.setAmount(MAX_ITEM_STACK_SIZE); - sourceItem.setAmount(itemsLeftOver - MAX_ITEM_STACK_SIZE); - } else { - // Cursor will be emptied - destItem.setAmount(itemsLeftOver); - session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session); - } - ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( - destSlot, - destItem.getItemStack() - ); - session.sendDownstreamPacket(creativeActionPacket); - affectedSlots.add(destSlot); - break; - } - } else { - // Delete the source since we're moving it - inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session); - ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( - sourceSlot, - new ItemStack(0) - ); - session.sendDownstreamPacket(creativeActionPacket); - affectedSlots.add(sourceSlot); - } - // Update the item count with however much the client took - newItem.setAmount(transferAction.getCount()); - // Remove that amount from the existing item - sourceItem.setAmount(sourceItem.getAmount() - transferAction.getCount()); - if (sourceItem.isEmpty()) { - // Item is basically deleted - if (sourceIsCursor) { - session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session); - } else { - inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session); - } - } - if (destIsCursor) { - session.getPlayerInventory().setCursor(newItem, session); - } else { - inventory.setItem(destSlot, newItem, session); - } - GeyserItemStack itemToUpdate = destIsCursor ? sourceItem : newItem; - // The Java server doesn't care about what's in the mouse in creative mode, so we just need to track - // which inventory slot the client modified - ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( - destIsCursor ? sourceSlot : destSlot, - itemToUpdate.isEmpty() ? new ItemStack(0) : itemToUpdate.getItemStack() - ); - session.sendDownstreamPacket(creativeActionPacket); - - if (!sourceIsCursor) { // Cursor is always added for us as an affected slot - affectedSlots.add(sourceSlot); - } - if (!destIsCursor) { - affectedSlots.add(destSlot); - } - } else if (isCursor(transferAction.getSource())) { //releasing cursor int sourceAmount = cursor.getAmount(); if (transferAction.getCount() == sourceAmount) { //release all @@ -359,35 +286,7 @@ public abstract class InventoryTranslator { if (!(checkNetId(session, inventory, swapAction.getSource()) && checkNetId(session, inventory, swapAction.getDestination()))) return rejectRequest(request); - if (session.getGameMode().equals(GameMode.CREATIVE) && inventory instanceof PlayerInventory) { - int destSlot = bedrockSlotToJava(swapAction.getDestination()); - GeyserItemStack oldSourceItem; - GeyserItemStack oldDestinationItem = inventory.getItem(destSlot); - if (isCursor(swapAction.getSource())) { - oldSourceItem = session.getPlayerInventory().getCursor(); - session.getPlayerInventory().setCursor(oldDestinationItem, session); - } else { - int sourceSlot = bedrockSlotToJava(swapAction.getSource()); - oldSourceItem = inventory.getItem(sourceSlot); - ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( - sourceSlot, - oldDestinationItem.isEmpty() ? new ItemStack(0) : oldDestinationItem.getItemStack() // isEmpty check... just in case - ); - session.sendDownstreamPacket(creativeActionPacket); - inventory.setItem(sourceSlot, oldDestinationItem, session); - } - if (isCursor(swapAction.getDestination())) { - session.getPlayerInventory().setCursor(oldSourceItem, session); - } else { - ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( - destSlot, - oldSourceItem.isEmpty() ? new ItemStack(0) : oldSourceItem.getItemStack() - ); - session.sendDownstreamPacket(creativeActionPacket); - inventory.setItem(destSlot, oldSourceItem, session); - } - - } else if (isCursor(swapAction.getSource()) && isCursor(swapAction.getDestination())) { //??? + if (isCursor(swapAction.getSource()) && isCursor(swapAction.getDestination())) { //??? return rejectRequest(request); } else if (isCursor(swapAction.getSource())) { //swap cursor int destSlot = bedrockSlotToJava(swapAction.getDestination()); @@ -425,29 +324,12 @@ public abstract class InventoryTranslator { return rejectRequest(request); if (isCursor(dropAction.getSource())) { //clicking outside of window - if (session.getGameMode() == GameMode.CREATIVE && inventory instanceof PlayerInventory) { - GeyserItemStack cursorItem = session.getPlayerInventory().getCursor(); - GeyserItemStack droppingItem = cursorItem.copy(); - // Subtract the cursor item by however much is being dropped - cursorItem.setAmount(cursorItem.getAmount() - dropAction.getCount()); - if (cursorItem.isEmpty()) { - // Cursor item no longer exists - session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session); - } - droppingItem.setAmount(dropAction.getCount()); - ClientCreativeInventoryActionPacket packet = new ClientCreativeInventoryActionPacket( - Click.OUTSIDE_SLOT, - droppingItem.getItemStack() - ); - session.sendDownstreamPacket(packet); - } else { - int sourceAmount = plan.getCursor().getAmount(); - if (dropAction.getCount() == sourceAmount) { //drop all - plan.add(Click.LEFT_OUTSIDE, Click.OUTSIDE_SLOT); - } else { //drop some - for (int i = 0; i < dropAction.getCount(); i++) { - plan.add(Click.RIGHT_OUTSIDE, Click.OUTSIDE_SLOT); //drop one until goal is met - } + int sourceAmount = plan.getCursor().getAmount(); + if (dropAction.getCount() == sourceAmount) { //drop all + plan.add(Click.LEFT_OUTSIDE, Click.OUTSIDE_SLOT); + } else { //drop some + for (int i = 0; i < dropAction.getCount(); i++) { + plan.add(Click.RIGHT_OUTSIDE, Click.OUTSIDE_SLOT); //drop one until goal is met } } } else { //dropping from inventory @@ -463,39 +345,6 @@ public abstract class InventoryTranslator { } break; } - case DESTROY: { - // Only called when a creative client wants to destroy an item... I think - Camotoy - DestroyStackRequestActionData destroyAction = (DestroyStackRequestActionData) action; - if (!session.getGameMode().equals(GameMode.CREATIVE)) { - // If this happens, let's throw an error and figure out why. - return rejectRequest(request); - } - if (!isCursor(destroyAction.getSource())) { - // Item exists; let's remove it from the inventory - int javaSlot = bedrockSlotToJava(destroyAction.getSource()); - GeyserItemStack existingItem = inventory.getItem(javaSlot); - existingItem.setAmount(existingItem.getAmount() - destroyAction.getCount()); - ClientCreativeInventoryActionPacket destroyItemPacket; - if (existingItem.isEmpty()) { - destroyItemPacket = new ClientCreativeInventoryActionPacket( - javaSlot, - new ItemStack(0) - ); - inventory.setItem(javaSlot, GeyserItemStack.EMPTY, session); - } else { - destroyItemPacket = new ClientCreativeInventoryActionPacket( - javaSlot, - existingItem.getItemStack() - ); - } - session.sendDownstreamPacket(destroyItemPacket); - affectedSlots.add(javaSlot); - } else { - // Just sync up the item on our end, since the server doesn't care what's in our cursor - session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session); - } - break; - } // The following three tend to be called for UI inventories case CONSUME: { if (inventory instanceof CartographyContainer) { @@ -838,73 +687,7 @@ public abstract class InventoryTranslator { } public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { - int creativeId = 0; - CraftState craftState = CraftState.START; - for (StackRequestActionData action : request.getActions()) { - switch (action.getType()) { - case CRAFT_CREATIVE: { - CraftCreativeStackRequestActionData creativeAction = (CraftCreativeStackRequestActionData) action; - if (craftState != CraftState.START) { - return rejectRequest(request); - } - craftState = CraftState.RECIPE_ID; - - creativeId = creativeAction.getCreativeItemNetworkId(); - break; - } - case CRAFT_RESULTS_DEPRECATED: { - CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action; - if (craftState != CraftState.RECIPE_ID) { - return rejectRequest(request); - } - craftState = CraftState.DEPRECATED; - break; - } - case TAKE: - case PLACE: { - TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; - if (craftState != CraftState.DEPRECATED) { - return rejectRequest(request); - } - craftState = CraftState.TRANSFER; - - if (transferAction.getSource().getContainer() != ContainerSlotType.CREATIVE_OUTPUT) { - return rejectRequest(request); - } - // Reference the creative items list we send to the client to know what it's asking of us - ItemData creativeItem = ItemRegistry.CREATIVE_ITEMS[creativeId - 1]; - // Get the correct count - creativeItem = ItemData.of(creativeItem.getId(), creativeItem.getDamage(), transferAction.getCount(), creativeItem.getTag()); - ItemStack javaCreativeItem = ItemTranslator.translateToJava(creativeItem); - - if (isCursor(transferAction.getDestination())) { - session.getPlayerInventory().setCursor(GeyserItemStack.from(javaCreativeItem), session); - return acceptRequest(request, Collections.singletonList( - new ItemStackResponsePacket.ContainerEntry(ContainerSlotType.CURSOR, - Collections.singletonList(makeItemEntry(0, session.getPlayerInventory().getCursor()))))); - } else { - int javaSlot = bedrockSlotToJava(transferAction.getDestination()); - GeyserItemStack existingItem = inventory.getItem(javaSlot); - if (existingItem.getJavaId() == javaCreativeItem.getId()) { - // Adding more to an existing item - existingItem.setAmount(existingItem.getAmount() + transferAction.getCount()); - javaCreativeItem = existingItem.getItemStack(); - } else { - inventory.setItem(javaSlot, GeyserItemStack.from(javaCreativeItem), session); - } - ClientCreativeInventoryActionPacket creativeActionPacket = new ClientCreativeInventoryActionPacket( - javaSlot, - javaCreativeItem - ); - session.sendDownstreamPacket(creativeActionPacket); - Set affectedSlots = Collections.singleton(javaSlot); - return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); - } - } - default: - return rejectRequest(request); - } - } + // Handled in PlayerInventoryTranslator return rejectRequest(request); } @@ -1040,11 +823,11 @@ public abstract class InventoryTranslator { return itemEntry; } - private static boolean isCursor(StackRequestSlotInfoData slotInfoData) { + protected static boolean isCursor(StackRequestSlotInfoData slotInfoData) { return slotInfoData.getContainer() == ContainerSlotType.CURSOR; } - private enum CraftState { + protected enum CraftState { START, RECIPE_ID, DEPRECATED, diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java index 51ff0f5d2..c5a2db1c8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java @@ -25,18 +25,26 @@ package org.geysermc.connector.network.translators.inventory.translators; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; import com.nukkitx.protocol.bedrock.data.inventory.*; +import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; +import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.inventory.SlotType; +import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.utils.InventoryUtils; import org.geysermc.connector.utils.LanguageUtils; @@ -207,6 +215,253 @@ public class PlayerInventoryTranslator extends InventoryTranslator { super.translateRequests(session, inventory, requests); } + @Override + public ItemStackResponsePacket.Response translateRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + if (session.getGameMode() != GameMode.CREATIVE) { + return super.translateRequest(session, inventory, request); + } + + PlayerInventory playerInv = session.getPlayerInventory(); + IntSet affectedSlots = new IntOpenHashSet(); + for (StackRequestActionData action : request.getActions()) { + switch (action.getType()) { + case TAKE: + case PLACE: { + TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; + if (!(checkNetId(session, inventory, transferAction.getSource()) && checkNetId(session, inventory, transferAction.getDestination()))) { + return rejectRequest(request); + } + if (isCraftingGrid(transferAction.getSource()) || isCraftingGrid(transferAction.getDestination())) { + return rejectRequest(request, false); + } + + int transferAmount = transferAction.getCount(); + if (isCursor(transferAction.getDestination())) { + int sourceSlot = bedrockSlotToJava(transferAction.getSource()); + GeyserItemStack sourceItem = inventory.getItem(sourceSlot); + if (playerInv.getCursor().isEmpty()) { + playerInv.setCursor(sourceItem.copy(0), session); + } + + playerInv.getCursor().add(transferAmount); + sourceItem.sub(transferAmount); + + affectedSlots.add(sourceSlot); + } else if (isCursor(transferAction.getSource())) { + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + GeyserItemStack sourceItem = playerInv.getCursor(); + if (inventory.getItem(destSlot).isEmpty()) { + inventory.setItem(destSlot, sourceItem.copy(0), session); + } + + inventory.getItem(destSlot).add(transferAmount); + sourceItem.sub(transferAmount); + + affectedSlots.add(destSlot); + } else { + int sourceSlot = bedrockSlotToJava(transferAction.getSource()); + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + GeyserItemStack sourceItem = inventory.getItem(sourceSlot); + if (inventory.getItem(destSlot).isEmpty()) { + inventory.setItem(destSlot, sourceItem.copy(0), session); + } + + inventory.getItem(destSlot).add(transferAmount); + sourceItem.sub(transferAmount); + + affectedSlots.add(sourceSlot); + affectedSlots.add(destSlot); + } + break; + } + case SWAP: { + SwapStackRequestActionData swapAction = (SwapStackRequestActionData) action; + if (!(checkNetId(session, inventory, swapAction.getSource()) && checkNetId(session, inventory, swapAction.getDestination()))) { + return rejectRequest(request); + } + if (isCraftingGrid(swapAction.getSource()) || isCraftingGrid(swapAction.getDestination())) { + return rejectRequest(request, false); + } + + if (isCursor(swapAction.getDestination())) { + int sourceSlot = bedrockSlotToJava(swapAction.getSource()); + GeyserItemStack sourceItem = inventory.getItem(sourceSlot); + GeyserItemStack destItem = playerInv.getCursor(); + + playerInv.setCursor(sourceItem, session); + inventory.setItem(sourceSlot, destItem, session); + + affectedSlots.add(sourceSlot); + } else if (isCursor(swapAction.getSource())) { + int destSlot = bedrockSlotToJava(swapAction.getDestination()); + GeyserItemStack sourceItem = playerInv.getCursor(); + GeyserItemStack destItem = inventory.getItem(destSlot); + + inventory.setItem(destSlot, sourceItem, session); + playerInv.setCursor(destItem, session); + + affectedSlots.add(destSlot); + } else { + int sourceSlot = bedrockSlotToJava(swapAction.getSource()); + int destSlot = bedrockSlotToJava(swapAction.getDestination()); + GeyserItemStack sourceItem = inventory.getItem(sourceSlot); + GeyserItemStack destItem = inventory.getItem(destSlot); + + inventory.setItem(destSlot, sourceItem, session); + inventory.setItem(sourceSlot, destItem, session); + + affectedSlots.add(sourceSlot); + affectedSlots.add(destSlot); + } + break; + } + case DROP: { + DropStackRequestActionData dropAction = (DropStackRequestActionData) action; + if (!checkNetId(session, inventory, dropAction.getSource())) { + return rejectRequest(request); + } + if (isCraftingGrid(dropAction.getSource())) { + return rejectRequest(request, false); + } + + GeyserItemStack sourceItem; + if (isCursor(dropAction.getSource())) { + sourceItem = playerInv.getCursor(); + } else { + int sourceSlot = bedrockSlotToJava(dropAction.getSource()); + sourceItem = inventory.getItem(sourceSlot); + affectedSlots.add(sourceSlot); + } + + if (sourceItem.isEmpty()) { + return rejectRequest(request); + } + + ClientCreativeInventoryActionPacket creativeDropPacket = new ClientCreativeInventoryActionPacket(-1, sourceItem.getItemStack(dropAction.getCount())); + session.sendDownstreamPacket(creativeDropPacket); + + sourceItem.sub(dropAction.getCount()); + break; + } + case DESTROY: { + // Only called when a creative client wants to destroy an item... I think - Camotoy + DestroyStackRequestActionData destroyAction = (DestroyStackRequestActionData) action; + if (!checkNetId(session, inventory, destroyAction.getSource())) { + return rejectRequest(request); + } + if (isCraftingGrid(destroyAction.getSource())) { + return rejectRequest(request, false); + } + + if (!isCursor(destroyAction.getSource())) { + // Item exists; let's remove it from the inventory + int javaSlot = bedrockSlotToJava(destroyAction.getSource()); + GeyserItemStack existingItem = inventory.getItem(javaSlot); + existingItem.sub(destroyAction.getCount()); + affectedSlots.add(javaSlot); + } else { + // Just sync up the item on our end, since the server doesn't care what's in our cursor + playerInv.getCursor().sub(destroyAction.getCount()); + } + break; + } + default: + return rejectRequest(request); + } + } + for (int slot : affectedSlots) { + sendCreativeAction(session, inventory, slot); + } + return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); + } + + @Override + public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + ItemStack javaCreativeItem = null; + IntSet affectedSlots = new IntOpenHashSet(); + CraftState craftState = CraftState.START; + for (StackRequestActionData action : request.getActions()) { + switch (action.getType()) { + case CRAFT_CREATIVE: { + CraftCreativeStackRequestActionData creativeAction = (CraftCreativeStackRequestActionData) action; + if (craftState != CraftState.START) { + return rejectRequest(request); + } + craftState = CraftState.RECIPE_ID; + + int creativeId = creativeAction.getCreativeItemNetworkId() - 1; + if (creativeId < 0 || creativeId >= ItemRegistry.CREATIVE_ITEMS.length) { + return rejectRequest(request); + } + // Reference the creative items list we send to the client to know what it's asking of us + ItemData creativeItem = ItemRegistry.CREATIVE_ITEMS[creativeId]; + javaCreativeItem = ItemTranslator.translateToJava(creativeItem); + break; + } + case CRAFT_RESULTS_DEPRECATED: { + CraftResultsDeprecatedStackRequestActionData deprecatedCraftAction = (CraftResultsDeprecatedStackRequestActionData) action; + if (craftState != CraftState.RECIPE_ID) { + return rejectRequest(request); + } + craftState = CraftState.DEPRECATED; + break; + } + case TAKE: + case PLACE: { + TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; + if (!(craftState == CraftState.DEPRECATED || craftState == CraftState.TRANSFER)) { + return rejectRequest(request); + } + craftState = CraftState.TRANSFER; + + if (transferAction.getSource().getContainer() != ContainerSlotType.CREATIVE_OUTPUT) { + return rejectRequest(request); + } + + if (isCursor(transferAction.getDestination())) { + if (session.getPlayerInventory().getCursor().isEmpty()) { + GeyserItemStack newItemStack = GeyserItemStack.from(javaCreativeItem); + newItemStack.setAmount(transferAction.getCount()); + session.getPlayerInventory().setCursor(newItemStack, session); + } else { + session.getPlayerInventory().getCursor().add(transferAction.getCount()); + } + //cursor is always included in response + } else { + int destSlot = bedrockSlotToJava(transferAction.getDestination()); + if (inventory.getItem(destSlot).isEmpty()) { + GeyserItemStack newItemStack = GeyserItemStack.from(javaCreativeItem); + newItemStack.setAmount(transferAction.getCount()); + inventory.setItem(destSlot, newItemStack, session); + } else { + inventory.getItem(destSlot).add(transferAction.getCount()); + } + affectedSlots.add(destSlot); + } + break; + } + default: + return rejectRequest(request); + } + } + for (int slot : affectedSlots) { + sendCreativeAction(session, inventory, slot); + } + return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); + } + + private static void sendCreativeAction(GeyserSession session, Inventory inventory, int slot) { + GeyserItemStack item = inventory.getItem(slot); + ItemStack itemStack = item.isEmpty() ? new ItemStack(-1, 0, null) : item.getItemStack(); + + ClientCreativeInventoryActionPacket creativePacket = new ClientCreativeInventoryActionPacket(slot, itemStack); + session.sendDownstreamPacket(creativePacket); + } + + private static boolean isCraftingGrid(StackRequestSlotInfoData slotInfoData) { + return slotInfoData.getContainer() == ContainerSlotType.CRAFTING_INPUT; + } + @Override public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { throw new UnsupportedOperationException(); From 2304c1375e39b9027e1f7de051973bcb69d627e9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 19 Jan 2021 18:22:07 -0500 Subject: [PATCH 052/161] [ci skip] Clarify strip NBT comment --- .../translators/java/JavaDeclareRecipesTranslator.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index 07b839f9c..45b300ceb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -68,7 +68,8 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator Date: Tue, 19 Jan 2021 19:01:51 -0500 Subject: [PATCH 053/161] Add some comments and fix book editing in creative --- .../bedrock/BedrockBookEditTranslator.java | 74 +++++++++---------- .../BedrockItemStackRequestTranslator.java | 3 + .../java/JavaDeclareRecipesTranslator.java | 6 ++ 3 files changed, 44 insertions(+), 39 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBookEditTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBookEditTranslator.java index 67778e822..8e2d77df7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBookEditTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBookEditTranslator.java @@ -26,7 +26,6 @@ package org.geysermc.connector.network.translators.bedrock; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientEditBookPacket; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.ListTag; @@ -54,51 +53,48 @@ public class BedrockBookEditTranslator extends PacketTranslator List pages = tag.contains("pages") ? new LinkedList<>(((ListTag) tag.get("pages")).getValue()) : new LinkedList<>(); int page = packet.getPageNumber(); - // Creative edits the NBT for us - if (session.getGameMode() != GameMode.CREATIVE) { - switch (packet.getAction()) { - case ADD_PAGE: { + switch (packet.getAction()) { + case ADD_PAGE: { + // Add empty pages in between + for (int i = pages.size(); i < page; i++) { + pages.add(i, new StringTag("", "")); + } + pages.add(page, new StringTag("", packet.getText())); + break; + } + // Called whenever a page is modified + case REPLACE_PAGE: { + if (page < pages.size()) { + pages.set(page, new StringTag("", packet.getText())); + } else { // Add empty pages in between for (int i = pages.size(); i < page; i++) { pages.add(i, new StringTag("", "")); } pages.add(page, new StringTag("", packet.getText())); - break; } - // Called whenever a page is modified - case REPLACE_PAGE: { - if (page < pages.size()) { - pages.set(page, new StringTag("", packet.getText())); - } else { - // Add empty pages in between - for (int i = pages.size(); i < page; i++) { - pages.add(i, new StringTag("", "")); - } - pages.add(page, new StringTag("", packet.getText())); - } - break; - } - case DELETE_PAGE: { - if (page < pages.size()) { - pages.remove(page); - } - break; - } - case SWAP_PAGES: { - int page2 = packet.getSecondaryPageNumber(); - if (page < pages.size() && page2 < pages.size()) { - Collections.swap(pages, page, page2); - } - break; - } - case SIGN_BOOK: { - tag.put(new StringTag("author", packet.getAuthor())); - tag.put(new StringTag("title", packet.getTitle())); - break; - } - default: - return; + break; } + case DELETE_PAGE: { + if (page < pages.size()) { + pages.remove(page); + } + break; + } + case SWAP_PAGES: { + int page2 = packet.getSecondaryPageNumber(); + if (page < pages.size() && page2 < pages.size()) { + Collections.swap(pages, page, page2); + } + break; + } + case SIGN_BOOK: { + tag.put(new StringTag("author", packet.getAuthor())); + tag.put(new StringTag("title", packet.getTitle())); + break; + } + default: + return; } // Remove empty pages at the end while (pages.size() > 0) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java index a08d87496..bdbb88eee 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemStackRequestTranslator.java @@ -32,6 +32,9 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +/** + * The packet sent for server-authoritative-style inventory transactions. + */ @Translator(packet = ItemStackRequestPacket.class) public class BedrockItemStackRequestTranslator extends PacketTranslator { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index 45b300ceb..ee8f03306 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -196,6 +196,12 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator, IntSet> squashedOptions = new HashMap<>(); for (int i = 0; i < ingredients.length; i++) { From 5d4b368e51bdda8e7d0843310e5ff9ff9070358d Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Wed, 20 Jan 2021 11:09:28 -0900 Subject: [PATCH 054/161] Fix inventory bug when respawning --- .../network/translators/java/JavaRespawnTranslator.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java index b986c7ef5..55fdcacd1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaRespawnTranslator.java @@ -35,6 +35,7 @@ import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.DimensionUtils; @Translator(packet = ServerRespawnPacket.class) @@ -48,7 +49,10 @@ public class JavaRespawnTranslator extends PacketTranslator // Max health must be divisible by two in bedrock entity.getAttributes().put(AttributeType.HEALTH, AttributeType.HEALTH.getAttribute(maxHealth, (maxHealth % 2 == 1 ? maxHealth + 1 : maxHealth))); - session.setOpenInventory(null); + session.addInventoryTask(() -> { + session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR); + session.setOpenInventory(null); + }); SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); playerGameTypePacket.setGamemode(packet.getGamemode().ordinal()); From 787e6fe5f7fb10d26d372510fd39c7fdd6a4bc59 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 20 Jan 2021 18:46:04 -0500 Subject: [PATCH 055/161] Fix enchantments when there are less than three options --- .../geysermc/connector/inventory/EnchantingContainer.java | 3 +++ .../geysermc/connector/inventory/GeyserEnchantOption.java | 8 ++------ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java b/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java index 8638e6eab..d11ca6788 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/EnchantingContainer.java @@ -47,6 +47,9 @@ public class EnchantingContainer extends Container { geyserEnchantOptions = new GeyserEnchantOption[3]; for (int i = 0; i < geyserEnchantOptions.length; i++) { geyserEnchantOptions[i] = new GeyserEnchantOption(i); + // Options cannot be null, so we build initial options + // GeyserSession can be safely null here because it's only needed for net IDs + enchantOptions[i] = geyserEnchantOptions[i].build(null); } } } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java index a643fc194..e9ad81a6a 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserEnchantOption.java @@ -67,12 +67,8 @@ public class GeyserEnchantOption { } public EnchantOptionData build(GeyserSession session) { - if (enchantLevel == -1) { - // Should not be sent to the client, as it is supposed to be empty - return null; - } return new EnchantOptionData(xpCost, javaIndex + 16, EMPTY, - Collections.singletonList(new EnchantData(bedrockEnchantIndex, enchantLevel)), EMPTY, - javaEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(javaEnchantIndex), session.getNextItemNetId()); + enchantLevel == -1 ? EMPTY : Collections.singletonList(new EnchantData(bedrockEnchantIndex, enchantLevel)), EMPTY, + javaEnchantIndex == -1 ? "unknown" : ENCHANT_NAMES.get(javaEnchantIndex), enchantLevel == -1 ? 0 : session.getNextItemNetId()); } } From 1676242c4f4b099e2a1586bc5270726fbd6ffccf Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 22 Jan 2021 13:36:27 -0500 Subject: [PATCH 056/161] Add precaution if lectern container is not open --- .../translators/bedrock/BedrockLecternUpdateTranslator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java index b7c8aa05c..cfcf762f8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java @@ -31,7 +31,6 @@ import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; -import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.LecternUpdatePacket; import org.geysermc.connector.inventory.LecternContainer; import org.geysermc.connector.network.session.GeyserSession; @@ -60,6 +59,10 @@ public class BedrockLecternUpdateTranslator extends PacketTranslator Date: Fri, 29 Jan 2021 00:36:43 -0500 Subject: [PATCH 057/161] Add more debug logging --- .../translators/inventory/InventoryTranslator.java | 8 ++------ .../inventory/translators/PlayerInventoryTranslator.java | 1 + 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index b6f22dc61..e5a9705bc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -32,10 +32,8 @@ import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.data.game.window.WindowType; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; @@ -55,8 +53,6 @@ import org.geysermc.connector.network.translators.inventory.translators.chest.Si import org.geysermc.connector.network.translators.inventory.translators.furnace.BlastFurnaceInventoryTranslator; import org.geysermc.connector.network.translators.inventory.translators.furnace.FurnaceInventoryTranslator; import org.geysermc.connector.network.translators.inventory.translators.furnace.SmokerInventoryTranslator; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.utils.InventoryUtils; import java.util.*; @@ -200,7 +196,7 @@ public abstract class InventoryTranslator { transferAction.getSource().getSlot() >= 28 && transferAction.getSource().getSlot() <= 31) { return rejectRequest(request, false); } - session.getConnector().getLogger().error("DEBUG: About to reject request."); + session.getConnector().getLogger().error("DEBUG: About to reject request made by " + session.getName()); session.getConnector().getLogger().error("Source: " + transferAction.getSource().toString() + " Result: " + checkNetId(session, inventory, transferAction.getSource())); session.getConnector().getLogger().error("Destination: " + transferAction.getDestination().toString() + " Result: " + checkNetId(session, inventory, transferAction.getDestination())); return rejectRequest(request); @@ -733,7 +729,7 @@ public abstract class InventoryTranslator { public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequestPacket.Request request, boolean throwError) { if (throwError) { // Currently for debugging, but might be worth it to keep in the future if something goes terribly wrong. - new Throwable("DEBUGGING: ItemStackRequest rejected").printStackTrace(); + new Throwable("DEBUGGING: ItemStackRequest rejected " + request.toString()).printStackTrace(); } return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.ERROR, request.getRequestId(), Collections.emptyList()); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java index c5a2db1c8..ffb97addd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java @@ -366,6 +366,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { break; } default: + session.getConnector().getLogger().error("Unknown crafting state induced by " + session.getName()); return rejectRequest(request); } } From 4a8d93752b1bef601cd9458b7c82faf9bad2631b Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Fri, 29 Jan 2021 03:34:56 -0900 Subject: [PATCH 058/161] Handle DESTROY creative action --- .../translators/PlayerInventoryTranslator.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java index ffb97addd..288f5926e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java @@ -407,6 +407,17 @@ public class PlayerInventoryTranslator extends InventoryTranslator { craftState = CraftState.DEPRECATED; break; } + case DESTROY: { + DestroyStackRequestActionData destroyAction = (DestroyStackRequestActionData) action; + if (craftState != CraftState.DEPRECATED) { + return rejectRequest(request); + } + + int sourceSlot = bedrockSlotToJava(destroyAction.getSource()); + inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session); //assume all creative destroy requests will empty the slot + affectedSlots.add(sourceSlot); + break; + } case TAKE: case PLACE: { TransferStackRequestActionData transferAction = (TransferStackRequestActionData) action; From 454896a7adadafb2728a72669608f032d46811b2 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 30 Jan 2021 16:05:27 -0500 Subject: [PATCH 059/161] Start work on using the world block for inventories --- .../connector/inventory/Container.java | 7 +++ .../inventory/Generic3X3Container.java | 41 ++++++++++++++ .../network/session/GeyserSession.java | 17 ++++-- ...BedrockInventoryTransactionTranslator.java | 5 +- .../BedrockLecternUpdateTranslator.java | 4 +- .../inventory/InventoryTranslator.java | 5 +- .../holder/BlockInventoryHolder.java | 55 +++++++++++++++---- .../AbstractBlockInventoryTranslator.java | 8 +-- .../translators/AnvilInventoryTranslator.java | 3 +- .../Generic3X3InventoryTranslator.java | 48 ++++++++++++++++ ...or.java => HopperInventoryTranslator.java} | 8 +-- .../LecternInventoryTranslator.java | 3 +- .../chest/DoubleChestInventoryTranslator.java | 26 +++++++-- .../chest/SingleChestInventoryTranslator.java | 5 +- .../java/world/JavaBlockChangeTranslator.java | 2 +- 15 files changed, 195 insertions(+), 42 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java rename connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/{GenericBlockInventoryTranslator.java => HopperInventoryTranslator.java} (85%) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Container.java b/connector/src/main/java/org/geysermc/connector/inventory/Container.java index 520a76ef3..5284868a0 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Container.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Container.java @@ -27,6 +27,7 @@ package org.geysermc.connector.inventory; import lombok.Getter; import lombok.NonNull; +import lombok.Setter; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; @@ -38,6 +39,12 @@ public class Container extends Inventory { private final PlayerInventory playerInventory; private final int containerSize; + /** + * Whether we are using a real block when opening this inventory. + */ + @Setter + private boolean isUsingRealBlock = false; + public Container(String title, int id, int size, PlayerInventory playerInventory) { super(title, id, size); this.playerInventory = playerInventory; diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java b/connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java new file mode 100644 index 000000000..8cc820ccc --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.inventory; + +import lombok.Getter; +import lombok.Setter; + +public class Generic3X3Container extends Container { + /** + * Whether we need to set the container type as {@link com.nukkitx.protocol.bedrock.data.inventory.ContainerType#DROPPER} + */ + @Getter @Setter + private boolean isDropper = false; + + public Generic3X3Container(String title, int id, int size, PlayerInventory playerInventory) { + super(title, id, size, playerInventory); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 17c6bd66c..5c3e921a6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -38,7 +38,6 @@ import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; -import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; @@ -73,10 +72,10 @@ import lombok.Setter; import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.inventory.Inventory; @@ -231,7 +230,15 @@ public class GeyserSession implements CommandSender { * Initialized as (0, 0, 0) so it is always not-null. */ @Setter - private Vector3i lastInteractionPosition = Vector3i.ZERO; + private Vector3i lastInteractionBlockPosition = Vector3i.ZERO; + + /** + * Stores the position of the player the last time they interacted. + * Used to verify that the player did not move since their last interaction.
+ * Initialized as (0, 0, 0) so it is always not-null. + */ + @Setter + private Vector3f lastInteractionPlayerPosition = Vector3f.ZERO; @Setter private Entity ridingVehicleEntity; @@ -239,15 +246,13 @@ public class GeyserSession implements CommandSender { @Setter private long lastWindowCloseTime = 0; - @Setter - private VillagerTrade[] villagerTrades; @Setter private long lastInteractedVillagerEid; @Setter private Int2ObjectMap craftingRecipes; private final Set unlockedRecipes; - private AtomicInteger lastRecipeNetId; + private final AtomicInteger lastRecipeNetId; /** * Saves a list of all stonecutter recipes, for use in a stonecutter inventory. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index d6c9badb9..79f658188 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -117,8 +117,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { @Override public void translate(LecternUpdatePacket packet, GeyserSession session) { - session.getConnector().getLogger().error(packet.toString()); if (packet.isDroppingBook()) { // Bedrock drops the book outside of the GUI. Java drops it in the GUI // So, we enter the GUI and then drop it! :) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index e5a9705bc..7fd24080e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -33,7 +33,6 @@ import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; @@ -94,8 +93,8 @@ public abstract class InventoryTranslator { put(WindowType.STONECUTTER, new StonecutterInventoryTranslator()); /* Generics */ - put(WindowType.GENERIC_3X3, new GenericBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER)); - put(WindowType.HOPPER, new GenericBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER)); + put(WindowType.GENERIC_3X3, new Generic3X3InventoryTranslator()); + put(WindowType.HOPPER, new HopperInventoryTranslator()); /* Lectern */ put(WindowType.LECTERN, new LecternInventoryTranslator()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index 5c49c723e..35b46fe63 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -26,39 +26,70 @@ package org.geysermc.connector.network.translators.inventory.holder; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.google.common.collect.Sets; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; -import lombok.AllArgsConstructor; +import org.geysermc.connector.inventory.Container; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import java.util.Collections; +import java.util.Set; + /** - * Manages the fake block we implement for each inventory. + * Manages the fake block we implement for each inventory, should we need to. + * This class will attempt to use a real block first, if possible. */ -@AllArgsConstructor public class BlockInventoryHolder extends InventoryHolder { - private final int blockId; + /** + * The default Bedrock block ID to use as a fake block + */ + private final int defaultBedrockBlockId; private final ContainerType containerType; + private final Set validBlocks; + + public BlockInventoryHolder(String javaBlockIdentifier, ContainerType containerType, String... validBlocks) { + int javaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier); + this.defaultBedrockBlockId = BlockTranslator.getBedrockBlockId(javaBlockState); + this.containerType = containerType; + if (validBlocks != null) { + this.validBlocks = Sets.newHashSet(validBlocks); + this.validBlocks.add(javaBlockIdentifier.split("\\[")[0]); + } else { + this.validBlocks = Collections.singleton(javaBlockIdentifier.split("\\[")[0]); + } + } @Override public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { - //TODO: Improve on this (for example, multiple block states). We need this for the beacon. - if (BlockTranslator.getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionPosition())) == blockId) { - inventory.setHolderPosition(session.getLastInteractionPosition()); - return; + // 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 (session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition())) { + // Then, check to see if the interacted block is valid for this inventory by ensuring the block state identifier is valid + int javaBlockId = session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition()); + String javaBlockString = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(javaBlockId, "minecraft:air").split("\\[")[0]; + if (this.validBlocks.contains(javaBlockString)) { + // We can safely use this block + inventory.setHolderPosition(session.getLastInteractionBlockPosition()); + ((Container) inventory).setUsingRealBlock(true); + return; + } } + + // Otherwise, time to conjure up a fake block! Vector3i position = session.getPlayerEntity().getPosition().toInt(); position = position.add(Vector3i.UP); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(position); - blockPacket.setRuntimeId(blockId); + blockPacket.setRuntimeId(defaultBedrockBlockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); inventory.setHolderPosition(position); @@ -86,10 +117,12 @@ public class BlockInventoryHolder extends InventoryHolder { @Override public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { - Vector3i holderPos = inventory.getHolderPosition(); - if (holderPos.equals(session.getLastInteractionPosition())) { + if (((Container) inventory).isUsingRealBlock()) { + // No need to reset a block since we didn't change any blocks return; } + + Vector3i holderPos = inventory.getHolderPosition(); Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); int realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java index 92710656d..8664dc192 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java @@ -31,7 +31,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; /** * Provided as a base for any inventory that requires a block for opening it @@ -45,11 +44,12 @@ public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTran * @param javaBlockIdentifier a Java block identifier that is used as a temporary block * @param containerType the container type of this inventory * @param updater updater + * @param additionalValidBlocks any other block identifiers that can safely use this inventory without a fake block */ - public AbstractBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater) { + public AbstractBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater, + String... additionalValidBlocks) { super(size); - int javaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier); - this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), containerType); + this.holder = new BlockInventoryHolder(javaBlockIdentifier, containerType, additionalValidBlocks); this.updater = updater; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java index 9d4fbfeec..0d2b62153 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java @@ -48,7 +48,8 @@ import org.geysermc.connector.network.translators.item.ItemTranslator; public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { public AnvilInventoryTranslator() { - super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, UIInventoryUpdater.INSTANCE); + super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, UIInventoryUpdater.INSTANCE, + "minecraft:chipped_anvil", "minecraft:damaged_anvil"); } /* 1.16.100 support start */ diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java new file mode 100644 index 000000000..daa47b61c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators; + +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import org.geysermc.connector.inventory.Generic3X3Container; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.PlayerInventory; +import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; + +/** + * Droppers and dispensers + */ +public class Generic3X3InventoryTranslator extends AbstractBlockInventoryTranslator { + //TODO allow droppers to use their own block - this requires changing the ContainerType to DROPPER + public Generic3X3InventoryTranslator() { + super(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, UIInventoryUpdater.INSTANCE); + } + + @Override + public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { + return new Generic3X3Container(name, windowId, this.size, playerInventory); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/HopperInventoryTranslator.java similarity index 85% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/HopperInventoryTranslator.java index 55df41c1e..f7525d958 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/HopperInventoryTranslator.java @@ -28,14 +28,14 @@ package org.geysermc.connector.network.translators.inventory.translators; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; -import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; /** * Implemented on top of any block that does not have special properties implemented */ -public class GenericBlockInventoryTranslator extends AbstractBlockInventoryTranslator { - public GenericBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType) { - super(size, javaBlockIdentifier, containerType, ContainerInventoryUpdater.INSTANCE); +public class HopperInventoryTranslator extends AbstractBlockInventoryTranslator { + public HopperInventoryTranslator() { + super(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, UIInventoryUpdater.INSTANCE); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java index 9ce0ee0d1..157f63164 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java @@ -98,7 +98,7 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { CompoundTag tag = geyserItemStack.getNbt(); if (tag != null) { // Position has to be the last interacted position... right? - Vector3i position = session.getLastInteractionPosition(); + Vector3i position = session.getLastInteractionBlockPosition(); // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); int pagesSize = ((ListTag) tag.get("pages")).size(); @@ -124,7 +124,6 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId()); session.sendDownstreamPacket(closeWindowPacket); InventoryUtils.closeInventory(session, inventory.getId()); - session.getConnector().getLogger().warning("Closing inventory"); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java index c27eacff5..81efc1540 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java @@ -32,28 +32,41 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; +import org.geysermc.connector.inventory.Container; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { - private final int blockId; + private final int defaultBedrockBlockId; public DoubleChestInventoryTranslator(int size) { super(size, 54); int javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); - this.blockId = BlockTranslator.getBedrockBlockId(javaBlockState); + this.defaultBedrockBlockId = BlockTranslator.getBedrockBlockId(javaBlockState); } @Override public void prepareInventory(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())) { + int javaBlockId = session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition()); + String[] javaBlockString = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(javaBlockId, "minecraft:air").split("\\["); + if (javaBlockString.length > 1 && (javaBlockString[0].equals("minecraft:chest") || javaBlockString[0].equals("minecraft:trapped_chest")) + && !javaBlockString[1].contains("type=single")) { + inventory.setHolderPosition(session.getLastInteractionBlockPosition()); + ((Container) inventory).setUsingRealBlock(true); + return; + } + } + Vector3i position = session.getPlayerEntity().getPosition().toInt().add(Vector3i.UP); Vector3i pairPosition = position.add(Vector3i.UNIT_X); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(position); - blockPacket.setRuntimeId(blockId); + blockPacket.setRuntimeId(defaultBedrockBlockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); @@ -73,7 +86,7 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(pairPosition); - blockPacket.setRuntimeId(blockId); + blockPacket.setRuntimeId(defaultBedrockBlockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); @@ -105,6 +118,11 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { @Override public void closeInventory(GeyserSession session, Inventory inventory) { + if (((Container) inventory).isUsingRealBlock()) { + // No need to reset a block since we didn't change any blocks + return; + } + Vector3i holderPos = inventory.getHolderPosition(); Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); int realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java index 9fc3c0165..0861f62fd 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java @@ -30,15 +30,14 @@ import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class SingleChestInventoryTranslator extends ChestInventoryTranslator { private final InventoryHolder holder; public SingleChestInventoryTranslator(int size) { super(size, 27); - int javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); - this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), ContainerType.CONTAINER); + this.holder = new BlockInventoryHolder("minecraft:chest[facing=north,type=single,waterlogged=false]", ContainerType.CONTAINER, + "minecraft:chest", "minecraft:ender_chest", "minecraft:trapped_chest"); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java index d74165b14..9bff2652e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java @@ -93,7 +93,7 @@ public class JavaBlockChangeTranslator extends PacketTranslator Date: Tue, 2 Feb 2021 17:40:28 +0100 Subject: [PATCH 060/161] Fix EndGateway translation issue (#1892) * Fix EndGateway translation issue. * Change contains check to instanceof LongTag as requested. * Add space after if as requested. Co-authored-by: Brave_Chicken --- .../block/entity/EndGatewayBlockEntityTranslator.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java index 2dbd75852..761d1c3ab 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/EndGatewayBlockEntityTranslator.java @@ -27,6 +27,8 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.LongTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtList; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; @@ -39,7 +41,10 @@ import java.util.LinkedHashMap; public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { @Override public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { - builder.put("Age", (int) ((long) tag.get("Age").getValue())); + Tag ageTag = tag.get("Age"); + if (ageTag instanceof LongTag) { + builder.put("Age", (int) ((long) ageTag.getValue())); + } // Java sometimes does not provide this tag, but Bedrock crashes if it doesn't exist // Linked coordinates IntList tagsList = new IntArrayList(); From 27fecd7ff7a762396c1337222ed4c9bd51f03dfe Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 3 Feb 2021 19:51:11 -0500 Subject: [PATCH 061/161] Update Sponge repository (#1890) See https://discord.com/channels/142425412096491520/303772747907989504/805571283546144808 (Sponge Discord) --- bootstrap/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index a5ad53cb5..a6a2a86de 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -22,7 +22,7 @@ sponge-repo - https://repo.spongepowered.org/maven + https://repo.spongepowered.org/repository/maven-public/ bungeecord-repo From 254990148c42919062a2ecac0edf1d7bd06faa6f Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 3 Feb 2021 19:54:12 -0500 Subject: [PATCH 062/161] Fix several world-related errors on Spigot 1.12 (#1886) - Fix v1.12R1 not being recognized as a world adapter - Return air if a chunk is not loaded --- bootstrap/spigot/pom.xml | 4 ++-- .../java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java | 2 +- .../world/manager/GeyserSpigot1_12NativeWorldManager.java | 4 ++-- .../spigot/world/manager/GeyserSpigot1_12WorldManager.java | 4 ++++ .../spigot/world/manager/GeyserSpigotNativeWorldManager.java | 4 ++-- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index 93eebc3d2..d1cba8aea 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -30,9 +30,9 @@ provided - org.geysermc.adapters + org.geysermc.geyser.adapters spigot-all - 1.0-SNAPSHOT + 1.1-SNAPSHOT diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index b85aa3133..2f8e0db24 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -28,7 +28,6 @@ package org.geysermc.platform.spigot; import com.github.steveice10.mc.protocol.MinecraftConstants; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; -import org.geysermc.adapters.spigot.SpigotAdapters; import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.bootstrap.GeyserBootstrap; @@ -40,6 +39,7 @@ import org.geysermc.connector.ping.GeyserLegacyPingPassthrough; import org.geysermc.connector.ping.IGeyserPingPassthrough; import org.geysermc.connector.utils.FileUtils; import org.geysermc.connector.utils.LanguageUtils; +import org.geysermc.geyser.adapters.spigot.SpigotAdapters; import org.geysermc.platform.spigot.command.GeyserSpigotCommandExecutor; import org.geysermc.platform.spigot.command.GeyserSpigotCommandManager; import org.geysermc.platform.spigot.command.SpigotCommandSender; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java index ae1992727..2cbec3273 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12NativeWorldManager.java @@ -27,10 +27,10 @@ package org.geysermc.platform.spigot.world.manager; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import org.geysermc.adapters.spigot.SpigotAdapters; -import org.geysermc.adapters.spigot.SpigotWorldAdapter; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.geyser.adapters.spigot.SpigotAdapters; +import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter; import us.myles.ViaVersion.api.Via; import us.myles.ViaVersion.protocols.protocol1_13to1_12_2.storage.BlockStorage; diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java index cb450f7f9..423156c97 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigot1_12WorldManager.java @@ -75,6 +75,10 @@ public class GeyserSpigot1_12WorldManager extends GeyserSpigotWorldManager { if (player == null) { return BlockTranslator.JAVA_AIR_ID; } + if (!player.getWorld().isChunkLoaded(x >> 4, z >> 4)) { + // Prevent nasty async errors if a player is loading in + return BlockTranslator.JAVA_AIR_ID; + } // Get block entity storage BlockStorage storage = Via.getManager().getConnection(player.getUniqueId()).get(BlockStorage.class); Block block = player.getWorld().getBlockAt(x, y, z); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java index c7e3a3d4f..469a38f17 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotNativeWorldManager.java @@ -27,10 +27,10 @@ package org.geysermc.platform.spigot.world.manager; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import org.geysermc.adapters.spigot.SpigotAdapters; -import org.geysermc.adapters.spigot.SpigotWorldAdapter; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.geyser.adapters.spigot.SpigotAdapters; +import org.geysermc.geyser.adapters.spigot.SpigotWorldAdapter; public class GeyserSpigotNativeWorldManager extends GeyserSpigotWorldManager { protected final SpigotWorldAdapter adapter; From c26a2baed6e99d8bc5d27be1b21cb895b5d21ef9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 3 Feb 2021 19:54:35 -0500 Subject: [PATCH 063/161] Add more metrics (#1896) - Add the Java version being used - Add the Minecraft server version being used, alongside the platform type that is using that version. Not used for proxies as those have to be on latest to support the latest Minecraft version --- .../platform/spigot/GeyserSpigotPlugin.java | 18 ++++++++++++++---- .../platform/sponge/GeyserSpongePlugin.java | 5 +++++ .../geysermc/connector/GeyserConnector.java | 14 ++++++++++++++ .../connector/bootstrap/GeyserBootstrap.java | 15 +++++++++++++++ .../geysermc/connector/metrics/Metrics.java | 7 +++++-- 5 files changed, 53 insertions(+), 6 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 2f8e0db24..1190057e9 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -69,6 +69,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { private GeyserConnector connector; + /** + * The Minecraft server version, formatted as 1.#.# + */ + private String minecraftVersion; + @Override public void onEnable() { // This is manually done instead of using Bukkit methods to save the config because otherwise comments get removed @@ -118,6 +123,9 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { geyserConfig.loadFloodgate(this); + // Turn "(MC: 1.16.4)" into 1.16.4. + this.minecraftVersion = Bukkit.getServer().getVersion().split("\\(MC: ")[1].split("\\)")[0]; + this.connector = GeyserConnector.start(PlatformType.SPIGOT, this); if (geyserConfig.isLegacyPingPassthrough()) { @@ -239,6 +247,11 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { return new GeyserSpigotDumpInfo(); } + @Override + public String getMinecraftServerVersion() { + return this.minecraftVersion; + } + public boolean isCompatible(String version, String whichVersion) { int[] currentVersion = parseVersion(version); int[] otherVersion = parseVersion(whichVersion); @@ -277,10 +290,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { * @return the server version before ViaVersion finishes initializing */ public ProtocolVersion getServerProtocolVersion() { - String bukkitVersion = Bukkit.getServer().getVersion(); - // Turn "(MC: 1.16.4)" into 1.16.4. - String version = bukkitVersion.split("\\(MC: ")[1].split("\\)")[0]; - return ProtocolVersion.getClosest(version); + return ProtocolVersion.getClosest(this.minecraftVersion); } /** diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java index 5b8bf54b2..142d48d29 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java @@ -163,4 +163,9 @@ public class GeyserSpongePlugin implements GeyserBootstrap { public BootstrapDumpInfo getDumpInfo() { return new GeyserSpongeDumpInfo(); } + + @Override + public String getMinecraftServerVersion() { + return Sponge.getPlatform().getMinecraftVersion().getName(); + } } diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index d1059627a..4ec87d7b6 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -244,6 +244,20 @@ public class GeyserConnector { } return valueMap; })); + + String minecraftVersion = bootstrap.getMinecraftServerVersion(); + if (minecraftVersion != null) { + Map> versionMap = new HashMap<>(); + Map platformMap = new HashMap<>(); + platformMap.put(platformType.getPlatformName(), 1); + versionMap.put(minecraftVersion, platformMap); + + metrics.addCustomChart(new Metrics.DrilldownPie("minecraftServerVersion", () -> { + // By the end, we should return, for example: + // 1.16.5 => (Spigot, 1) + return versionMap; + })); + } } boolean isGui = false; diff --git a/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java b/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java index 7308fb674..59dc58c2b 100644 --- a/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java +++ b/connector/src/main/java/org/geysermc/connector/bootstrap/GeyserBootstrap.java @@ -33,6 +33,7 @@ import org.geysermc.connector.command.CommandManager; import org.geysermc.connector.network.translators.world.GeyserWorldManager; import org.geysermc.connector.network.translators.world.WorldManager; +import javax.annotation.Nullable; import java.nio.file.Path; public interface GeyserBootstrap { @@ -99,4 +100,18 @@ public interface GeyserBootstrap { * @return The info about the bootstrap */ BootstrapDumpInfo getDumpInfo(); + + /** + * Returns the Minecraft version currently being used on the server. This should be only be implemented on platforms + * that have direct server access - platforms such as proxies always have to be on their latest version to support + * the newest Minecraft version, but older servers can use ViaVersion to enable newer versions to join. + *
+ * If used, this should not be null before {@link org.geysermc.connector.GeyserConnector} initialization. + * + * @return the Minecraft version being used on the server, or null if not applicable + */ + @Nullable + default String getMinecraftServerVersion() { + return null; + } } diff --git a/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java b/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java index 25ae5a36c..1457780b1 100644 --- a/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java +++ b/connector/src/main/java/org/geysermc/connector/metrics/Metrics.java @@ -36,6 +36,7 @@ import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -75,7 +76,7 @@ public class Metrics { private final static ObjectMapper mapper = new ObjectMapper(); - private GeyserConnector connector; + private final GeyserConnector connector; /** * Class constructor. @@ -156,6 +157,7 @@ public class Metrics { String osName = System.getProperty("os.name"); String osArch = System.getProperty("os.arch"); String osVersion = System.getProperty("os.version"); + String javaVersion = System.getProperty("java.version"); int coreCount = Runtime.getRuntime().availableProcessors(); ObjectNode data = mapper.createObjectNode(); @@ -163,6 +165,7 @@ public class Metrics { data.put("serverUUID", serverUUID); data.put("playerAmount", playerAmount); + data.put("javaVersion", javaVersion); data.put("osName", osName); data.put("osArch", osArch); data.put("osVersion", osVersion); @@ -241,7 +244,7 @@ public class Metrics { } ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); GZIPOutputStream gzip = new GZIPOutputStream(outputStream); - gzip.write(str.getBytes("UTF-8")); + gzip.write(str.getBytes(StandardCharsets.UTF_8)); gzip.close(); return outputStream.toByteArray(); } From 8670172f8ddc5599c6c93a74ce814b654b0369e9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 5 Feb 2021 11:48:12 -0500 Subject: [PATCH 064/161] Revert "Start work on using the world block for inventories" Oops! Not finished yet!! This reverts commit 454896a7adadafb2728a72669608f032d46811b2. --- .../connector/inventory/Container.java | 7 --- .../inventory/Generic3X3Container.java | 41 -------------- .../network/session/GeyserSession.java | 17 ++---- ...BedrockInventoryTransactionTranslator.java | 5 +- .../BedrockLecternUpdateTranslator.java | 4 +- .../inventory/InventoryTranslator.java | 5 +- .../holder/BlockInventoryHolder.java | 55 ++++--------------- .../AbstractBlockInventoryTranslator.java | 8 +-- .../translators/AnvilInventoryTranslator.java | 3 +- .../Generic3X3InventoryTranslator.java | 48 ---------------- ...a => GenericBlockInventoryTranslator.java} | 8 +-- .../LecternInventoryTranslator.java | 3 +- .../chest/DoubleChestInventoryTranslator.java | 26 ++------- .../chest/SingleChestInventoryTranslator.java | 5 +- .../java/world/JavaBlockChangeTranslator.java | 2 +- 15 files changed, 42 insertions(+), 195 deletions(-) delete mode 100644 connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java rename connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/{HopperInventoryTranslator.java => GenericBlockInventoryTranslator.java} (85%) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Container.java b/connector/src/main/java/org/geysermc/connector/inventory/Container.java index 5284868a0..520a76ef3 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Container.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Container.java @@ -27,7 +27,6 @@ package org.geysermc.connector.inventory; import lombok.Getter; import lombok.NonNull; -import lombok.Setter; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; @@ -39,12 +38,6 @@ public class Container extends Inventory { private final PlayerInventory playerInventory; private final int containerSize; - /** - * Whether we are using a real block when opening this inventory. - */ - @Setter - private boolean isUsingRealBlock = false; - public Container(String title, int id, int size, PlayerInventory playerInventory) { super(title, id, size); this.playerInventory = playerInventory; diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java b/connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java deleted file mode 100644 index 8cc820ccc..000000000 --- a/connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.inventory; - -import lombok.Getter; -import lombok.Setter; - -public class Generic3X3Container extends Container { - /** - * Whether we need to set the container type as {@link com.nukkitx.protocol.bedrock.data.inventory.ContainerType#DROPPER} - */ - @Getter @Setter - private boolean isDropper = false; - - public Generic3X3Container(String title, int id, int size, PlayerInventory playerInventory) { - super(title, id, size, playerInventory); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 5c3e921a6..17c6bd66c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -38,6 +38,7 @@ import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; +import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; @@ -72,10 +73,10 @@ import lombok.Setter; import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.inventory.Inventory; @@ -230,15 +231,7 @@ public class GeyserSession implements CommandSender { * Initialized as (0, 0, 0) so it is always not-null. */ @Setter - private Vector3i lastInteractionBlockPosition = Vector3i.ZERO; - - /** - * Stores the position of the player the last time they interacted. - * Used to verify that the player did not move since their last interaction.
- * Initialized as (0, 0, 0) so it is always not-null. - */ - @Setter - private Vector3f lastInteractionPlayerPosition = Vector3f.ZERO; + private Vector3i lastInteractionPosition = Vector3i.ZERO; @Setter private Entity ridingVehicleEntity; @@ -246,13 +239,15 @@ public class GeyserSession implements CommandSender { @Setter private long lastWindowCloseTime = 0; + @Setter + private VillagerTrade[] villagerTrades; @Setter private long lastInteractedVillagerEid; @Setter private Int2ObjectMap craftingRecipes; private final Set unlockedRecipes; - private final AtomicInteger lastRecipeNetId; + private AtomicInteger lastRecipeNetId; /** * Saves a list of all stonecutter recipes, for use in a stonecutter inventory. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 79f658188..d6c9badb9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -117,9 +117,8 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { @Override public void translate(LecternUpdatePacket packet, GeyserSession session) { + session.getConnector().getLogger().error(packet.toString()); if (packet.isDroppingBook()) { // Bedrock drops the book outside of the GUI. Java drops it in the GUI // So, we enter the GUI and then drop it! :) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 7fd24080e..e5a9705bc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -33,6 +33,7 @@ import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; @@ -93,8 +94,8 @@ public abstract class InventoryTranslator { put(WindowType.STONECUTTER, new StonecutterInventoryTranslator()); /* Generics */ - put(WindowType.GENERIC_3X3, new Generic3X3InventoryTranslator()); - put(WindowType.HOPPER, new HopperInventoryTranslator()); + put(WindowType.GENERIC_3X3, new GenericBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER)); + put(WindowType.HOPPER, new GenericBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER)); /* Lectern */ put(WindowType.LECTERN, new LecternInventoryTranslator()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index 35b46fe63..5c49c723e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -26,70 +26,39 @@ package org.geysermc.connector.network.translators.inventory.holder; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.google.common.collect.Sets; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; -import org.geysermc.connector.inventory.Container; +import lombok.AllArgsConstructor; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import java.util.Collections; -import java.util.Set; - /** - * Manages the fake block we implement for each inventory, should we need to. - * This class will attempt to use a real block first, if possible. + * Manages the fake block we implement for each inventory. */ +@AllArgsConstructor public class BlockInventoryHolder extends InventoryHolder { - /** - * The default Bedrock block ID to use as a fake block - */ - private final int defaultBedrockBlockId; + private final int blockId; private final ContainerType containerType; - private final Set validBlocks; - - public BlockInventoryHolder(String javaBlockIdentifier, ContainerType containerType, String... validBlocks) { - int javaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier); - this.defaultBedrockBlockId = BlockTranslator.getBedrockBlockId(javaBlockState); - this.containerType = containerType; - if (validBlocks != null) { - this.validBlocks = Sets.newHashSet(validBlocks); - this.validBlocks.add(javaBlockIdentifier.split("\\[")[0]); - } else { - this.validBlocks = Collections.singleton(javaBlockIdentifier.split("\\[")[0]); - } - } @Override public void 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 (session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition())) { - // Then, check to see if the interacted block is valid for this inventory by ensuring the block state identifier is valid - int javaBlockId = session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition()); - String javaBlockString = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(javaBlockId, "minecraft:air").split("\\[")[0]; - if (this.validBlocks.contains(javaBlockString)) { - // We can safely use this block - inventory.setHolderPosition(session.getLastInteractionBlockPosition()); - ((Container) inventory).setUsingRealBlock(true); - return; - } + //TODO: Improve on this (for example, multiple block states). We need this for the beacon. + if (BlockTranslator.getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionPosition())) == blockId) { + inventory.setHolderPosition(session.getLastInteractionPosition()); + return; } - - // Otherwise, time to conjure up a fake block! Vector3i position = session.getPlayerEntity().getPosition().toInt(); position = position.add(Vector3i.UP); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(position); - blockPacket.setRuntimeId(defaultBedrockBlockId); + blockPacket.setRuntimeId(blockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); inventory.setHolderPosition(position); @@ -117,12 +86,10 @@ public class BlockInventoryHolder extends InventoryHolder { @Override public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { - if (((Container) inventory).isUsingRealBlock()) { - // No need to reset a block since we didn't change any blocks + Vector3i holderPos = inventory.getHolderPosition(); + if (holderPos.equals(session.getLastInteractionPosition())) { return; } - - Vector3i holderPos = inventory.getHolderPosition(); Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); int realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java index 8664dc192..92710656d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java @@ -31,6 +31,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; /** * Provided as a base for any inventory that requires a block for opening it @@ -44,12 +45,11 @@ public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTran * @param javaBlockIdentifier a Java block identifier that is used as a temporary block * @param containerType the container type of this inventory * @param updater updater - * @param additionalValidBlocks any other block identifiers that can safely use this inventory without a fake block */ - public AbstractBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater, - String... additionalValidBlocks) { + public AbstractBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater) { super(size); - this.holder = new BlockInventoryHolder(javaBlockIdentifier, containerType, additionalValidBlocks); + int javaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier); + this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), containerType); this.updater = updater; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java index 0d2b62153..9d4fbfeec 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java @@ -48,8 +48,7 @@ import org.geysermc.connector.network.translators.item.ItemTranslator; public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { public AnvilInventoryTranslator() { - super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, UIInventoryUpdater.INSTANCE, - "minecraft:chipped_anvil", "minecraft:damaged_anvil"); + super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, UIInventoryUpdater.INSTANCE); } /* 1.16.100 support start */ diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java deleted file mode 100644 index daa47b61c..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.inventory.translators; - -import com.github.steveice10.mc.protocol.data.game.window.WindowType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import org.geysermc.connector.inventory.Generic3X3Container; -import org.geysermc.connector.inventory.Inventory; -import org.geysermc.connector.inventory.PlayerInventory; -import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; - -/** - * Droppers and dispensers - */ -public class Generic3X3InventoryTranslator extends AbstractBlockInventoryTranslator { - //TODO allow droppers to use their own block - this requires changing the ContainerType to DROPPER - public Generic3X3InventoryTranslator() { - super(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, UIInventoryUpdater.INSTANCE); - } - - @Override - public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { - return new Generic3X3Container(name, windowId, this.size, playerInventory); - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/HopperInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java similarity index 85% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/HopperInventoryTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java index f7525d958..55df41c1e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/HopperInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java @@ -28,14 +28,14 @@ package org.geysermc.connector.network.translators.inventory.translators; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; -import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; /** * Implemented on top of any block that does not have special properties implemented */ -public class HopperInventoryTranslator extends AbstractBlockInventoryTranslator { - public HopperInventoryTranslator() { - super(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, UIInventoryUpdater.INSTANCE); +public class GenericBlockInventoryTranslator extends AbstractBlockInventoryTranslator { + public GenericBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType) { + super(size, javaBlockIdentifier, containerType, ContainerInventoryUpdater.INSTANCE); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java index 157f63164..9ce0ee0d1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java @@ -98,7 +98,7 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { CompoundTag tag = geyserItemStack.getNbt(); if (tag != null) { // Position has to be the last interacted position... right? - Vector3i position = session.getLastInteractionBlockPosition(); + Vector3i position = session.getLastInteractionPosition(); // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); int pagesSize = ((ListTag) tag.get("pages")).size(); @@ -124,6 +124,7 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId()); session.sendDownstreamPacket(closeWindowPacket); InventoryUtils.closeInventory(session, inventory.getId()); + session.getConnector().getLogger().warning("Closing inventory"); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java index 81efc1540..c27eacff5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java @@ -32,41 +32,28 @@ import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; -import org.geysermc.connector.inventory.Container; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { - private final int defaultBedrockBlockId; + private final int blockId; public DoubleChestInventoryTranslator(int size) { super(size, 54); int javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); - this.defaultBedrockBlockId = BlockTranslator.getBedrockBlockId(javaBlockState); + this.blockId = BlockTranslator.getBedrockBlockId(javaBlockState); } @Override public void prepareInventory(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())) { - int javaBlockId = session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition()); - String[] javaBlockString = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(javaBlockId, "minecraft:air").split("\\["); - if (javaBlockString.length > 1 && (javaBlockString[0].equals("minecraft:chest") || javaBlockString[0].equals("minecraft:trapped_chest")) - && !javaBlockString[1].contains("type=single")) { - inventory.setHolderPosition(session.getLastInteractionBlockPosition()); - ((Container) inventory).setUsingRealBlock(true); - return; - } - } - Vector3i position = session.getPlayerEntity().getPosition().toInt().add(Vector3i.UP); Vector3i pairPosition = position.add(Vector3i.UNIT_X); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(position); - blockPacket.setRuntimeId(defaultBedrockBlockId); + blockPacket.setRuntimeId(blockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); @@ -86,7 +73,7 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(pairPosition); - blockPacket.setRuntimeId(defaultBedrockBlockId); + blockPacket.setRuntimeId(blockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); @@ -118,11 +105,6 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { @Override public void closeInventory(GeyserSession session, Inventory inventory) { - if (((Container) inventory).isUsingRealBlock()) { - // No need to reset a block since we didn't change any blocks - return; - } - Vector3i holderPos = inventory.getHolderPosition(); Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); int realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java index 0861f62fd..9fc3c0165 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java @@ -30,14 +30,15 @@ import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class SingleChestInventoryTranslator extends ChestInventoryTranslator { private final InventoryHolder holder; public SingleChestInventoryTranslator(int size) { super(size, 27); - this.holder = new BlockInventoryHolder("minecraft:chest[facing=north,type=single,waterlogged=false]", ContainerType.CONTAINER, - "minecraft:chest", "minecraft:ender_chest", "minecraft:trapped_chest"); + int javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); + this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), ContainerType.CONTAINER); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java index 9bff2652e..d74165b14 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java @@ -93,7 +93,7 @@ public class JavaBlockChangeTranslator extends PacketTranslator Date: Sat, 6 Feb 2021 20:06:45 -0500 Subject: [PATCH 065/161] Use static commit of Protocol --- connector/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 5be089afd..37218bda2 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -30,9 +30,9 @@ compile - com.nukkitx.protocol + com.github.CloudburstMC.Protocol bedrock-v422 - 2.6.1-SNAPSHOT + 89617b7689 compile From 66fcd87ce6573cd7040cfd49f3720a4a0dd98a25 Mon Sep 17 00:00:00 2001 From: bundabrg Date: Mon, 8 Feb 2021 10:18:20 +0800 Subject: [PATCH 066/161] Remove extra bracket from BOUYANCY_DATA (#1903) Co-authored-by: bundabrg --- .../src/main/java/org/geysermc/connector/entity/BoatEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java index 1afcf08d3..ed5272b89 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java @@ -43,7 +43,7 @@ public class BoatEntity extends Entity { */ private static final String BUOYANCY_DATA = "{\"apply_gravity\":true,\"base_buoyancy\":1.0,\"big_wave_probability\":0.02999999932944775," + "\"big_wave_speed\":10.0,\"drag_down_on_buoyancy_removed\":0.0,\"liquid_blocks\":[\"minecraft:water\"," + - "\"minecraft:flowing_water\"],\"simulate_waves\":false}}"; + "\"minecraft:flowing_water\"],\"simulate_waves\":false}"; private boolean isPaddlingLeft; private float paddleTimeLeft; From 3140d3ced76e4a4c66b6e3cb3ec833e788d9d296 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 7 Feb 2021 22:57:44 -0500 Subject: [PATCH 067/161] Allow Geyser to boot if it cannot download en_US file (#1907) This commit allows Geyser to start if it is unable to download Minecraft's English locale file, instead of throwing an exception that stops it. --- .../main/java/org/geysermc/connector/utils/LocaleUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index f2ec43bf6..15a52cf7f 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -101,7 +101,7 @@ public class LocaleUtils { ASSET_MAP.put(entry.getKey(), asset); } } catch (Exception e) { - GeyserConnector.getInstance().getLogger().info(LanguageUtils.getLocaleStringLog("geyser.locale.fail.asset_cache", (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace()))); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.locale.fail.asset_cache", (!e.getMessage().isEmpty() ? e.getMessage() : e.getStackTrace()))); } } @@ -260,7 +260,7 @@ public class LocaleUtils { // Delete the nolonger needed client/server jar Files.delete(tmpFilePath); } catch (Exception e) { - throw new AssertionError(LanguageUtils.getLocaleStringLog("geyser.locale.fail.en_us"), e); + GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.locale.fail.en_us"), e); } } From 1ec589fa357c8929786c1b0438ce07e5fe01bce4 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 8 Feb 2021 13:30:37 -0500 Subject: [PATCH 068/161] Enable debug property in Log4J config if applicable (#1906) With this commit, debug messages in Netty and Protocol will now show if debug mode is enabled in the Geyser standalone config. There is also some small tuning to the PacketTranslatorRegistry for cleanliness and maybe some minor performance. --- .../platform/standalone/GeyserStandaloneBootstrap.java | 9 ++++----- .../network/translators/PacketTranslatorRegistry.java | 10 +++++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java index 5aa15635e..551b0e584 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/GeyserStandaloneBootstrap.java @@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedField; import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; import lombok.Getter; import net.minecrell.terminalconsole.TerminalConsoleAppender; +import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.Appender; import org.apache.logging.log4j.core.Logger; @@ -167,11 +168,6 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { this.onEnable(); } - public void onEnable(boolean useGui) { - this.useGui = useGui; - this.onEnable(); - } - @Override public void onEnable() { Logger logger = (Logger) LogManager.getRootLogger(); @@ -213,6 +209,9 @@ public class GeyserStandaloneBootstrap implements GeyserBootstrap { } GeyserConfiguration.checkGeyserConfiguration(geyserConfig, geyserLogger); + // Allow libraries like Protocol to have their debug information passthrough + logger.get().setLevel(geyserConfig.isDebugMode() ? Level.DEBUG : Level.INFO); + connector = GeyserConnector.start(PlatformType.STANDALONE, this); geyserCommandManager = new GeyserCommandManager(connector); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java index b841a79b2..eb26dbff9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/PacketTranslatorRegistry.java @@ -30,6 +30,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdate import com.github.steveice10.packetlib.packet.Packet; import com.nukkitx.protocol.bedrock.BedrockPacket; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.geysermc.common.PlatformType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.FileUtils; @@ -89,12 +90,15 @@ public class PacketTranslatorRegistry { public

boolean translate(Class clazz, P packet, GeyserSession session) { if (!session.getUpstream().isClosed() && !session.isClosed()) { try { - if (translators.containsKey(clazz)) { - ((PacketTranslator

) translators.get(clazz)).translate(packet, session); + PacketTranslator

translator = (PacketTranslator

) translators.get(clazz); + if (translator != null) { + translator.translate(packet, session); return true; } else { - if (!IGNORED_PACKETS.contains(clazz)) + if ((GeyserConnector.getInstance().getPlatformType() != PlatformType.STANDALONE || !(packet instanceof BedrockPacket)) && !IGNORED_PACKETS.contains(clazz)) { + // Other debug logs already take care of Bedrock packets for us if on standalone GeyserConnector.getInstance().getLogger().debug("Could not find packet for " + (packet.toString().length() > 25 ? packet.getClass().getSimpleName() : packet)); + } } } catch (Throwable ex) { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.network.translator.packet.failed", packet.getClass().getSimpleName()), ex); From b16e1d6b4340d6ee295eae9bc05fe5cfa0e9d01d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 10 Feb 2021 12:18:31 -0500 Subject: [PATCH 069/161] Threading and ping improvements (#1870) - Don't schedule for setting jumping on and off if cache chunks is enabled, since we don't need to know that - Add a new setting to disable player ping forwarding. Hopefully this helps with timeouts. --- .../geysermc/connector/GeyserConnector.java | 6 ++--- .../configuration/GeyserConfiguration.java | 2 ++ .../GeyserJacksonConfiguration.java | 3 +++ .../network/session/GeyserSession.java | 9 +++++-- .../BedrockNetworkStackLatencyTranslator.java | 24 ++++++++++--------- .../player/BedrockActionTranslator.java | 9 +++---- .../java/JavaKeepAliveTranslator.java | 3 +++ .../connector/utils/CooldownUtils.java | 9 +++---- connector/src/main/resources/config.yml | 6 ++++- 9 files changed, 43 insertions(+), 28 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 4ec87d7b6..f86e0b1e6 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -56,10 +56,7 @@ import org.geysermc.connector.network.translators.world.WorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator; -import org.geysermc.connector.utils.DimensionUtils; -import org.geysermc.connector.utils.LanguageUtils; -import org.geysermc.connector.utils.LocaleUtils; -import org.geysermc.connector.utils.ResourcePack; +import org.geysermc.connector.utils.*; import javax.naming.directory.Attribute; import javax.naming.directory.InitialDirContext; @@ -190,6 +187,7 @@ public class GeyserConnector { remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort); authType = AuthType.getByName(config.getRemote().getAuthType()); + CooldownUtils.setShowCooldown(config.isShowCooldown()); DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether SkullBlockEntityTranslator.ALLOW_CUSTOM_SKULLS = config.isAllowCustomSkulls(); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index e21aa6bb8..31bcbe995 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -59,6 +59,8 @@ public interface GeyserConfiguration { int getPingPassthroughInterval(); + boolean isForwardPlayerPing(); + int getMaxPlayers(); boolean isDebugMode(); diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 7c9532ff8..4e03da52f 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -74,6 +74,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("ping-passthrough-interval") private int pingPassthroughInterval = 3; + @JsonProperty("forward-player-ping") + private boolean forwardPlayerPing = false; + @JsonProperty("max-players") private int maxPlayers = 100; diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 104e72cd3..2f15cab75 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -170,6 +170,9 @@ public class GeyserSession implements CommandSender { @Setter private boolean sprinting; + /** + * Not updated if cache chunks is enabled. + */ @Setter private boolean jumping; @@ -567,8 +570,10 @@ public class GeyserSession implements CommandSender { downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true); downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress()); } - // Let Geyser handle sending the keep alive - downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false); + if (connector.getConfig().isForwardPlayerPing()) { + // Let Geyser handle sending the keep alive + downstream.getSession().setFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, false); + } downstream.getSession().addListener(new SessionAdapter() { @Override public void packetSending(PacketSendingEvent event) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java index 38e5981e6..56387fd58 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockNetworkStackLatencyTranslator.java @@ -33,23 +33,25 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.floodgate.util.DeviceOS; /** - * Used to send the keep alive packet back to the server + * Used to send the forwarded keep alive packet back to the server */ @Translator(packet = NetworkStackLatencyPacket.class) public class BedrockNetworkStackLatencyTranslator extends PacketTranslator { @Override public void translate(NetworkStackLatencyPacket packet, GeyserSession session) { - long pingId; - // so apparently, as of 1.16.200 - // PS4 divides the network stack latency timestamp FOR US!!! - // WTF - if (session.getClientData().getDeviceOS().equals(DeviceOS.NX)) { - // Ignore the weird DeviceOS, our order is wrong and will be fixed in Floodgate 2.0 - pingId = packet.getTimestamp(); - } else { - pingId = packet.getTimestamp() / 1000; + if (session.getConnector().getConfig().isForwardPlayerPing()) { + long pingId; + // so apparently, as of 1.16.200 + // PS4 divides the network stack latency timestamp FOR US!!! + // WTF + if (session.getClientData().getDeviceOS().equals(DeviceOS.NX)) { + // Ignore the weird DeviceOS, our order is wrong and will be fixed in Floodgate 2.0 + pingId = packet.getTimestamp(); + } else { + pingId = packet.getTimestamp() / 1000; + } + session.sendDownstreamPacket(new ClientKeepAlivePacket(pingId)); } - session.sendDownstreamPacket(new ClientKeepAlivePacket(pingId)); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java index c4dbbec40..789df85c2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java @@ -205,10 +205,11 @@ public class BedrockActionTranslator extends PacketTranslator { - session.setJumping(false); - }, 1, TimeUnit.SECONDS); + if (!session.getConnector().getConfig().isCacheChunks()) { + // Save the jumping status for determining teleport status + session.setJumping(true); + session.getConnector().getGeneralThreadPool().schedule(() -> session.setJumping(false), 1, TimeUnit.SECONDS); + } break; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java index 76e9b0958..8506389f3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaKeepAliveTranslator.java @@ -39,6 +39,9 @@ public class JavaKeepAliveTranslator extends PacketTranslator Date: Fri, 12 Feb 2021 19:11:05 +0100 Subject: [PATCH 070/161] Update Adventure to 4.5.0 (#1920) --- connector/pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index a7001be00..8703c3117 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -175,25 +175,25 @@ net.kyori adventure-api - 4.3.0 + 4.5.0 compile net.kyori adventure-text-serializer-gson - 4.3.0 + 4.5.0 compile net.kyori adventure-text-serializer-legacy - 4.3.0 + 4.5.0 compile net.kyori adventure-text-serializer-gson-legacy-impl - 4.3.0 + 4.5.0 compile From 98b2a0e51b418df3f34ca3fb80e6557479e1a288 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 12 Feb 2021 14:16:29 -0500 Subject: [PATCH 071/161] Fix NPE with MOTD (#1921) --- .../connector/network/ConnectorServerEventHandler.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java index 87883087d..b4a0ddb69 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -95,6 +95,13 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { pong.setMaximumPlayerCount(config.getMaxPlayers()); } + if (pong.getMotd() == null) { + pong.setMotd(""); + } + if (pong.getSubMotd() == null) { + pong.setSubMotd(""); + } + // The ping will not appear if the MOTD + sub-MOTD is of a certain length. // We don't know why, though byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8); From 78337830c6929b759dc6c71dd872d1e945b949fd Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 12 Feb 2021 14:39:41 -0500 Subject: [PATCH 072/161] Use the real block for opening inventory when possible --- .../connector/inventory/Container.java | 15 ++++ .../inventory/Generic3X3Container.java | 48 +++++++++++++ .../network/session/GeyserSession.java | 17 +++-- ...BedrockInventoryTransactionTranslator.java | 5 +- .../BedrockLecternUpdateTranslator.java | 4 +- .../inventory/InventoryTranslator.java | 5 +- .../holder/BlockInventoryHolder.java | 60 +++++++++++++--- .../AbstractBlockInventoryTranslator.java | 8 +-- .../translators/AnvilInventoryTranslator.java | 3 +- .../Generic3X3InventoryTranslator.java | 71 +++++++++++++++++++ ...or.java => HopperInventoryTranslator.java} | 6 +- .../LecternInventoryTranslator.java | 3 +- .../chest/DoubleChestInventoryTranslator.java | 47 ++++++++++-- .../chest/SingleChestInventoryTranslator.java | 5 +- .../java/world/JavaBlockChangeTranslator.java | 2 +- .../DoubleChestBlockEntityTranslator.java | 52 ++++++++------ 16 files changed, 290 insertions(+), 61 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java rename connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/{GenericBlockInventoryTranslator.java => HopperInventoryTranslator.java} (87%) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Container.java b/connector/src/main/java/org/geysermc/connector/inventory/Container.java index 520a76ef3..6302a7daa 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Container.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Container.java @@ -38,6 +38,11 @@ public class Container extends Inventory { private final PlayerInventory playerInventory; private final int containerSize; + /** + * Whether we are using a real block when opening this inventory. + */ + private boolean isUsingRealBlock = false; + public Container(String title, int id, int size, PlayerInventory playerInventory) { super(title, id, size); this.playerInventory = playerInventory; @@ -66,4 +71,14 @@ public class Container extends Inventory { public int getSize() { return this.containerSize; } + + /** + * Will be overwritten for droppers. + * + * @param usingRealBlock whether this container is using a real container or not + * @param javaBlockId the Java block string of the block, if real + */ + public void setUsingRealBlock(boolean usingRealBlock, String javaBlockId) { + isUsingRealBlock = usingRealBlock; + } } diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java b/connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java new file mode 100644 index 000000000..be8d13a74 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.inventory; + +import lombok.Getter; + +public class Generic3X3Container extends Container { + /** + * Whether we need to set the container type as {@link com.nukkitx.protocol.bedrock.data.inventory.ContainerType#DROPPER} + */ + @Getter + private boolean isDropper = false; + + public Generic3X3Container(String title, int id, int size, PlayerInventory playerInventory) { + super(title, id, size, playerInventory); + } + + @Override + public void setUsingRealBlock(boolean usingRealBlock, String javaBlockId) { + super.setUsingRealBlock(usingRealBlock, javaBlockId); + if (usingRealBlock) { + isDropper = javaBlockId.startsWith("minecraft:dropper"); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 15357ada3..00d654764 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -38,7 +38,6 @@ import com.github.steveice10.mc.protocol.data.SubProtocol; import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.statistic.Statistic; -import com.github.steveice10.mc.protocol.data.game.window.VillagerTrade; import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; @@ -73,10 +72,10 @@ import lombok.Setter; import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.inventory.Inventory; @@ -234,7 +233,15 @@ public class GeyserSession implements CommandSender { * Initialized as (0, 0, 0) so it is always not-null. */ @Setter - private Vector3i lastInteractionPosition = Vector3i.ZERO; + private Vector3i lastInteractionBlockPosition = Vector3i.ZERO; + + /** + * Stores the position of the player the last time they interacted. + * Used to verify that the player did not move since their last interaction.
+ * Initialized as (0, 0, 0) so it is always not-null. + */ + @Setter + private Vector3f lastInteractionPlayerPosition = Vector3f.ZERO; @Setter private Entity ridingVehicleEntity; @@ -242,15 +249,13 @@ public class GeyserSession implements CommandSender { @Setter private long lastWindowCloseTime = 0; - @Setter - private VillagerTrade[] villagerTrades; @Setter private long lastInteractedVillagerEid; @Setter private Int2ObjectMap craftingRecipes; private final Set unlockedRecipes; - private AtomicInteger lastRecipeNetId; + private final AtomicInteger lastRecipeNetId; /** * Saves a list of all stonecutter recipes, for use in a stonecutter inventory. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index d6c9badb9..79f658188 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -117,8 +117,9 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { @Override public void translate(LecternUpdatePacket packet, GeyserSession session) { - session.getConnector().getLogger().error(packet.toString()); if (packet.isDroppingBook()) { // Bedrock drops the book outside of the GUI. Java drops it in the GUI // So, we enter the GUI and then drop it! :) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index e5a9705bc..7fd24080e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -33,7 +33,6 @@ import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; @@ -94,8 +93,8 @@ public abstract class InventoryTranslator { put(WindowType.STONECUTTER, new StonecutterInventoryTranslator()); /* Generics */ - put(WindowType.GENERIC_3X3, new GenericBlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER)); - put(WindowType.HOPPER, new GenericBlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER)); + put(WindowType.GENERIC_3X3, new Generic3X3InventoryTranslator()); + put(WindowType.HOPPER, new HopperInventoryTranslator()); /* Lectern */ put(WindowType.LECTERN, new LecternInventoryTranslator()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index 5c49c723e..c6da2aeb2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -26,43 +26,79 @@ package org.geysermc.connector.network.translators.inventory.holder; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.google.common.collect.Sets; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; -import lombok.AllArgsConstructor; +import org.geysermc.connector.inventory.Container; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import java.util.Collections; +import java.util.Set; + /** - * Manages the fake block we implement for each inventory. + * Manages the fake block we implement for each inventory, should we need to. + * This class will attempt to use a real block first, if possible. */ -@AllArgsConstructor public class BlockInventoryHolder extends InventoryHolder { - private final int blockId; + /** + * The default Bedrock block ID to use as a fake block + */ + private final int defaultBedrockBlockId; private final ContainerType containerType; + private final Set validBlocks; + + public BlockInventoryHolder(String javaBlockIdentifier, ContainerType containerType, String... validBlocks) { + int javaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier); + this.defaultBedrockBlockId = BlockTranslator.getBedrockBlockId(javaBlockState); + this.containerType = containerType; + if (validBlocks != null) { + this.validBlocks = Sets.newHashSet(validBlocks); + this.validBlocks.add(javaBlockIdentifier.split("\\[")[0]); + } else { + this.validBlocks = Collections.singleton(javaBlockIdentifier.split("\\[")[0]); + } + } @Override public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { - //TODO: Improve on this (for example, multiple block states). We need this for the beacon. - if (BlockTranslator.getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionPosition())) == blockId) { - inventory.setHolderPosition(session.getLastInteractionPosition()); - return; + // 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 (session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition())) { + // Then, check to see if the interacted block is valid for this inventory by ensuring the block state identifier is valid + int javaBlockId = session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition()); + String javaBlockString = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(javaBlockId, "minecraft:air").split("\\[")[0]; + if (this.validBlocks.contains(javaBlockString)) { + // We can safely use this block + inventory.setHolderPosition(session.getLastInteractionBlockPosition()); + ((Container) inventory).setUsingRealBlock(true, javaBlockString); + setCustomName(session, session.getLastInteractionBlockPosition(), inventory); + return; + } } + + // Otherwise, time to conjure up a fake block! Vector3i position = session.getPlayerEntity().getPosition().toInt(); position = position.add(Vector3i.UP); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(position); - blockPacket.setRuntimeId(blockId); + blockPacket.setRuntimeId(defaultBedrockBlockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); inventory.setHolderPosition(position); + setCustomName(session, position, inventory); + } + + protected void setCustomName(GeyserSession session, Vector3i position, Inventory inventory) { NbtMap tag = NbtMap.builder() .putInt("x", position.getX()) .putInt("y", position.getY()) @@ -86,10 +122,12 @@ public class BlockInventoryHolder extends InventoryHolder { @Override public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { - Vector3i holderPos = inventory.getHolderPosition(); - if (holderPos.equals(session.getLastInteractionPosition())) { + if (((Container) inventory).isUsingRealBlock()) { + // No need to reset a block since we didn't change any blocks return; } + + Vector3i holderPos = inventory.getHolderPosition(); Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); int realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java index 92710656d..8664dc192 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java @@ -31,7 +31,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; /** * Provided as a base for any inventory that requires a block for opening it @@ -45,11 +44,12 @@ public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTran * @param javaBlockIdentifier a Java block identifier that is used as a temporary block * @param containerType the container type of this inventory * @param updater updater + * @param additionalValidBlocks any other block identifiers that can safely use this inventory without a fake block */ - public AbstractBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater) { + public AbstractBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType, InventoryUpdater updater, + String... additionalValidBlocks) { super(size); - int javaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier); - this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), containerType); + this.holder = new BlockInventoryHolder(javaBlockIdentifier, containerType, additionalValidBlocks); this.updater = updater; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java index 9d4fbfeec..0d2b62153 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java @@ -48,7 +48,8 @@ import org.geysermc.connector.network.translators.item.ItemTranslator; public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { public AnvilInventoryTranslator() { - super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, UIInventoryUpdater.INSTANCE); + super(3, "minecraft:anvil[facing=north]", ContainerType.ANVIL, UIInventoryUpdater.INSTANCE, + "minecraft:chipped_anvil", "minecraft:damaged_anvil"); } /* 1.16.100 support start */ diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java new file mode 100644 index 000000000..eb417c51e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.inventory.translators; + +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; +import org.geysermc.connector.inventory.Generic3X3Container; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.PlayerInventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; + +/** + * Droppers and dispensers + */ +public class Generic3X3InventoryTranslator extends AbstractBlockInventoryTranslator { + + public Generic3X3InventoryTranslator() { + super(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, ContainerInventoryUpdater.INSTANCE, + "minecraft:dropper"); + } + + @Override + public Inventory createInventory(String name, int windowId, WindowType windowType, PlayerInventory playerInventory) { + return new Generic3X3Container(name, windowId, this.size, playerInventory); + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); + containerOpenPacket.setId((byte) inventory.getId()); + containerOpenPacket.setType(((Generic3X3Container) inventory).isDropper() ? ContainerType.DROPPER : ContainerType.DISPENSER); + containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); + containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); + session.sendUpstreamPacket(containerOpenPacket); + } + + @Override + public BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot) { + if (javaSlot < this.size) { + return new BedrockContainerSlot(ContainerSlotType.CONTAINER, javaSlot); + } + return super.javaSlotToBedrockContainer(javaSlot); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/HopperInventoryTranslator.java similarity index 87% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/HopperInventoryTranslator.java index 55df41c1e..7f067d1c0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GenericBlockInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/HopperInventoryTranslator.java @@ -33,9 +33,9 @@ import org.geysermc.connector.network.translators.inventory.updater.ContainerInv /** * Implemented on top of any block that does not have special properties implemented */ -public class GenericBlockInventoryTranslator extends AbstractBlockInventoryTranslator { - public GenericBlockInventoryTranslator(int size, String javaBlockIdentifier, ContainerType containerType) { - super(size, javaBlockIdentifier, containerType, ContainerInventoryUpdater.INSTANCE); +public class HopperInventoryTranslator extends AbstractBlockInventoryTranslator { + public HopperInventoryTranslator() { + super(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, ContainerInventoryUpdater.INSTANCE); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java index 9ce0ee0d1..157f63164 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java @@ -98,7 +98,7 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { CompoundTag tag = geyserItemStack.getNbt(); if (tag != null) { // Position has to be the last interacted position... right? - Vector3i position = session.getLastInteractionPosition(); + Vector3i position = session.getLastInteractionBlockPosition(); // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); int pagesSize = ((ListTag) tag.get("pages")).size(); @@ -124,7 +124,6 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId()); session.sendDownstreamPacket(closeWindowPacket); InventoryUtils.closeInventory(session, inventory.getId()); - session.getConnector().getLogger().warning("Closing inventory"); } } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java index c27eacff5..d1e628fce 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java @@ -28,32 +28,66 @@ package org.geysermc.connector.network.translators.inventory.translators.chest; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; +import org.geysermc.connector.inventory.Container; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.network.translators.world.block.DoubleChestValue; +import org.geysermc.connector.network.translators.world.block.entity.DoubleChestBlockEntityTranslator; public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { - private final int blockId; + private final int defaultBedrockBlockId; public DoubleChestInventoryTranslator(int size) { super(size, 54); int javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); - this.blockId = BlockTranslator.getBedrockBlockId(javaBlockState); + this.defaultBedrockBlockId = BlockTranslator.getBedrockBlockId(javaBlockState); } @Override public void prepareInventory(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())) { + int javaBlockId = session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition()); + String[] javaBlockString = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(javaBlockId, "minecraft:air").split("\\["); + if (javaBlockString.length > 1 && (javaBlockString[0].equals("minecraft:chest") || javaBlockString[0].equals("minecraft:trapped_chest")) + && !javaBlockString[1].contains("type=single")) { + inventory.setHolderPosition(session.getLastInteractionBlockPosition()); + ((Container) inventory).setUsingRealBlock(true, javaBlockString[0]); + + NbtMapBuilder tag = NbtMap.builder() + .putString("id", "Chest") + .putInt("x", session.getLastInteractionBlockPosition().getX()) + .putInt("y", session.getLastInteractionBlockPosition().getY()) + .putInt("z", session.getLastInteractionBlockPosition().getZ()) + .putString("CustomName", inventory.getTitle()) + .putString("id", "Chest"); + + DoubleChestValue chestValue = BlockStateValues.getDoubleChestValues().get(javaBlockId); + DoubleChestBlockEntityTranslator.translateChestValue(tag, chestValue, + session.getLastInteractionBlockPosition().getX(), session.getLastInteractionBlockPosition().getZ()); + + BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); + dataPacket.setData(tag.build()); + dataPacket.setBlockPosition(session.getLastInteractionBlockPosition()); + session.sendUpstreamPacket(dataPacket); + return; + } + } + Vector3i position = session.getPlayerEntity().getPosition().toInt().add(Vector3i.UP); Vector3i pairPosition = position.add(Vector3i.UNIT_X); UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(position); - blockPacket.setRuntimeId(blockId); + blockPacket.setRuntimeId(defaultBedrockBlockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); @@ -73,7 +107,7 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(pairPosition); - blockPacket.setRuntimeId(blockId); + blockPacket.setRuntimeId(defaultBedrockBlockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); @@ -105,6 +139,11 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { @Override public void closeInventory(GeyserSession session, Inventory inventory) { + if (((Container) inventory).isUsingRealBlock()) { + // No need to reset a block since we didn't change any blocks + return; + } + Vector3i holderPos = inventory.getHolderPosition(); Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); int realBlock = session.getConnector().getWorldManager().getBlockAt(session, pos.getX(), pos.getY(), pos.getZ()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java index 9fc3c0165..7b9b12c4f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java @@ -30,15 +30,14 @@ import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; import org.geysermc.connector.network.translators.inventory.holder.InventoryHolder; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class SingleChestInventoryTranslator extends ChestInventoryTranslator { private final InventoryHolder holder; public SingleChestInventoryTranslator(int size) { super(size, 27); - int javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); - this.holder = new BlockInventoryHolder(BlockTranslator.getBedrockBlockId(javaBlockState), ContainerType.CONTAINER); + this.holder = new BlockInventoryHolder("minecraft:chest[facing=north,type=single,waterlogged=false]", ContainerType.CONTAINER, + "minecraft:ender_chest", "minecraft:trapped_chest"); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java index d74165b14..9bff2652e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java @@ -93,7 +93,7 @@ public class JavaBlockChangeTranslator extends PacketTranslator Date: Fri, 12 Feb 2021 14:43:29 -0500 Subject: [PATCH 073/161] [ci skip] Formatting fixes --- .../inventory/translators/Generic3X3InventoryTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java index eb417c51e..133ec635d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/Generic3X3InventoryTranslator.java @@ -40,7 +40,6 @@ import org.geysermc.connector.network.translators.inventory.updater.ContainerInv * Droppers and dispensers */ public class Generic3X3InventoryTranslator extends AbstractBlockInventoryTranslator { - public Generic3X3InventoryTranslator() { super(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, ContainerInventoryUpdater.INSTANCE, "minecraft:dropper"); @@ -55,6 +54,7 @@ public class Generic3X3InventoryTranslator extends AbstractBlockInventoryTransla public void openInventory(GeyserSession session, Inventory inventory) { ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); containerOpenPacket.setId((byte) inventory.getId()); + // Required for opening the real block - otherwise, if the container type is incorrect, it refuses to open containerOpenPacket.setType(((Generic3X3Container) inventory).isDropper() ? ContainerType.DROPPER : ContainerType.DISPENSER); containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); From 52ddf8c556721bc97bcf16700c409ace66edd99f Mon Sep 17 00:00:00 2001 From: Tim203 Date: Fri, 12 Feb 2021 22:22:45 +0100 Subject: [PATCH 074/161] Moved skin uploading to the global api --- .../geysermc/floodgate/util/BedrockData.java | 27 ++- .../org/geysermc/floodgate/util/RawSkin.java | 106 --------- .../floodgate/util/WebsocketEventType.java | 40 ++++ connector/pom.xml | 5 + .../geysermc/connector/GeyserConnector.java | 3 + .../network/session/GeyserSession.java | 21 +- .../network/session/auth/AuthData.java | 22 +- .../session/auth/BedrockClientData.java | 67 ------ .../connector/skin/FloodgateSkinUploader.java | 205 ++++++++++++++++++ .../geysermc/connector/utils/Constants.java | 43 ++++ .../connector/utils/LoginEncryptionUtils.java | 3 +- .../connector/utils/PluginMessageUtils.java | 21 +- 12 files changed, 363 insertions(+), 200 deletions(-) delete mode 100644 common/src/main/java/org/geysermc/floodgate/util/RawSkin.java create mode 100644 common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java create mode 100644 connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java create mode 100644 connector/src/main/java/org/geysermc/connector/utils/Constants.java diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 5f449fe2d..4ea5ee78e 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -37,7 +37,7 @@ import lombok.Getter; @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) public final class BedrockData implements Cloneable { - public static final int EXPECTED_LENGTH = 10; + public static final int EXPECTED_LENGTH = 12; private final String version; private final String username; @@ -50,19 +50,24 @@ public final class BedrockData implements Cloneable { private final LinkedPlayer linkedPlayer; private final boolean fromProxy; + private final int subscribeId; + private final String verifyCode; + private final int dataLength; public static BedrockData of(String version, String username, String xuid, int deviceOs, String languageCode, int uiProfile, int inputMode, String ip, - LinkedPlayer linkedPlayer, boolean fromProxy) { + LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, + String verifyCode) { return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, - uiProfile, ip, linkedPlayer, fromProxy, EXPECTED_LENGTH); + uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, EXPECTED_LENGTH); } public static BedrockData of(String version, String username, String xuid, int deviceOs, - String languageCode, int uiProfile, int inputMode, String ip) { - return of(version, username, xuid, deviceOs, languageCode, - uiProfile, inputMode, ip, null, false); + String languageCode, int uiProfile, int inputMode, String ip, + int subscribeId, String verifyCode) { + return of(version, username, xuid, deviceOs, languageCode, uiProfile, inputMode, ip, null, + false, subscribeId, verifyCode); } public static BedrockData fromString(String data) { @@ -75,13 +80,14 @@ public final class BedrockData implements Cloneable { // The format is the same as the order of the fields in this class return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], - Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], - linkedPlayer, "1".equals(split[8]), split.length + Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], linkedPlayer, + "1".equals(split[9]), Integer.parseInt(split[10]), split[11], split.length ); } private static BedrockData emptyData(int dataLength) { - return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, dataLength); + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, + dataLength); } public boolean hasPlayerLink() { @@ -94,7 +100,8 @@ public final class BedrockData implements Cloneable { return version + '\0' + username + '\0' + xuid + '\0' + deviceOs + '\0' + languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + (fromProxy ? 1 : 0) + '\0' + - (linkedPlayer != null ? linkedPlayer.toString() : "null"); + (linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' + + subscribeId + '\0' + verifyCode; } @Override diff --git a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java b/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java deleted file mode 100644 index 470b7e24a..000000000 --- a/common/src/main/java/org/geysermc/floodgate/util/RawSkin.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2019-2020 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.floodgate.util; - -import lombok.AllArgsConstructor; - -import java.nio.ByteBuffer; -import java.util.Base64; - -import static java.lang.String.format; - -@AllArgsConstructor -public final class RawSkin { - public int width; - public int height; - public byte[] data; - public boolean alex; - - private RawSkin() { - } - - public static RawSkin decode(byte[] data, int offset) throws InvalidFormatException { - if (data == null || offset < 0 || data.length <= offset) { - return null; - } - if (offset == 0) { - return decode(data); - } - - byte[] rawSkin = new byte[data.length - offset]; - System.arraycopy(data, offset, rawSkin, 0, rawSkin.length); - return decode(rawSkin); - } - - public static RawSkin decode(byte[] data) throws InvalidFormatException { - // offset is an amount of bytes before the Base64 starts - if (data == null) { - return null; - } - - int maxEncodedLength = Base64Utils.getEncodedLength(64 * 64 * 4 + 9); - // if the RawSkin is longer then the max Java Edition skin length - if (data.length > maxEncodedLength) { - throw new InvalidFormatException(format( - "Encoded data cannot be longer then %s bytes! Got %s", - maxEncodedLength, data.length - )); - } - - // if the encoded data doesn't even contain the width, height (8 bytes, 2 ints) and isAlex - if (data.length < Base64Utils.getEncodedLength(9)) { - throw new InvalidFormatException("Encoded data must be at least 16 bytes long!"); - } - - data = Base64.getDecoder().decode(data); - - ByteBuffer buffer = ByteBuffer.wrap(data); - - RawSkin skin = new RawSkin(); - skin.width = buffer.getInt(); - skin.height = buffer.getInt(); - if (buffer.remaining() - 1 != (skin.width * skin.height * 4)) { - throw new InvalidFormatException(format( - "Expected skin length to be %s, got %s", - (skin.width * skin.height * 4), buffer.remaining() - )); - } - skin.data = new byte[buffer.remaining() - 1]; - buffer.get(skin.data); - skin.alex = buffer.get() == 1; - return skin; - } - - public byte[] encode() { - // 2 x int + 1 = 9 bytes - ByteBuffer buffer = ByteBuffer.allocate(9 + data.length); - buffer.putInt(width); - buffer.putInt(height); - buffer.put(data); - buffer.put((byte) (alex ? 1 : 0)); - return Base64.getEncoder().encode(buffer.array()); - } -} diff --git a/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java new file mode 100644 index 000000000..1527f2360 --- /dev/null +++ b/common/src/main/java/org/geysermc/floodgate/util/WebsocketEventType.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.floodgate.util; + +public enum WebsocketEventType { + SUBSCRIBER_CREATED, + SUBSCRIBERS_COUNT, + ADDED_TO_QUEUE, + SKIN_UPLOADED, + CREATOR_DISCONNECTED; + + public static final WebsocketEventType[] VALUES = values(); + + public static WebsocketEventType getById(int id) { + return VALUES.length > id ? VALUES[id] : null; + } +} diff --git a/connector/pom.xml b/connector/pom.xml index 5e78fcfc3..04fc918fa 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -22,6 +22,11 @@ 2.9.8 compile
+ + org.java-websocket + Java-Websocket + 1.5.1 + com.fasterxml.jackson.datatype jackson-datatype-jsr310 diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 38c2aa29a..4dc78df1c 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -56,6 +56,7 @@ import org.geysermc.connector.network.translators.world.WorldManager; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; import org.geysermc.connector.network.translators.world.block.entity.SkullBlockEntityTranslator; +import org.geysermc.connector.skin.FloodgateSkinUploader; import org.geysermc.connector.utils.DimensionUtils; import org.geysermc.connector.utils.LanguageUtils; import org.geysermc.connector.utils.LocaleUtils; @@ -108,6 +109,7 @@ public class GeyserConnector { private AuthType authType; private FloodgateCipher cipher; + private FloodgateSkinUploader skinUploader; private boolean shuttingDown = false; @@ -203,6 +205,7 @@ public class GeyserConnector { cipher = new AesCipher(new Base64Topping()); cipher.init(key); logger.info(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.loaded_key")); + skinUploader = new FloodgateSkinUploader(this).start(); } catch (Exception exception) { logger.severe(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.bad_key"), exception); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 8bdb7f2b1..722cd2e45 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -67,10 +67,10 @@ import lombok.Getter; import lombok.NonNull; import lombok.Setter; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.inventory.PlayerInventory; @@ -85,6 +85,7 @@ import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.collision.CollisionManager; import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.skin.FloodgateSkinUploader; import org.geysermc.connector.skin.SkinManager; import org.geysermc.connector.utils.*; import org.geysermc.cumulus.Form; @@ -553,7 +554,9 @@ public class GeyserSession implements CommandSender { byte[] encryptedData; try { + FloodgateSkinUploader skinUploader = connector.getSkinUploader(); FloodgateCipher cipher = connector.getCipher(); + encryptedData = cipher.encryptFromString(BedrockData.of( clientData.getGameVersion(), authData.getName(), @@ -562,7 +565,9 @@ public class GeyserSession implements CommandSender { clientData.getLanguageCode(), clientData.getUiProfile().ordinal(), clientData.getCurrentInputMode().ordinal(), - upstream.getSession().getAddress().getAddress().getHostAddress() + upstream.getSession().getAddress().getAddress().getHostAddress(), + skinUploader.getId(), + skinUploader.getVerifyCode() ).toString()); } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); @@ -570,13 +575,7 @@ public class GeyserSession implements CommandSender { return; } - byte[] rawSkin = clientData.getAndTransformImage("Skin").encode(); - byte[] finalData = new byte[encryptedData.length + rawSkin.length + 1]; - System.arraycopy(encryptedData, 0, finalData, 0, encryptedData.length); - finalData[encryptedData.length] = 0x21; // splitter - System.arraycopy(rawSkin, 0, finalData, encryptedData.length + 1, rawSkin.length); - - String finalDataString = new String(finalData, StandardCharsets.UTF_8); + String finalDataString = new String(encryptedData, StandardCharsets.UTF_8); HandshakePacket handshakePacket = event.getPacket(); event.setPacket(new HandshakePacket( @@ -639,6 +638,10 @@ public class GeyserSession implements CommandSender { if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) { SkinManager.handleBedrockSkin(playerEntity, clientData); } + + // We'll send the skin upload a bit after the handshake packet (aka this packet), + // because otherwise the global server returns the data too fast. + getAuthData().upload(connector); } PacketTranslatorRegistry.JAVA_TRANSLATOR.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java index ba9e13548..463276891 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/AuthData.java @@ -25,16 +25,26 @@ package org.geysermc.connector.network.session.auth; -import lombok.AllArgsConstructor; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.geysermc.connector.GeyserConnector; import java.util.UUID; -@Getter -@AllArgsConstructor +@RequiredArgsConstructor public class AuthData { + @Getter private final String name; + @Getter private final UUID UUID; + @Getter private final String xboxUUID; - private String name; - private UUID UUID; - private String xboxUUID; + private final JsonNode certChainData; + private final String clientData; + + public void upload(GeyserConnector connector) { + // we can't upload the skin in LoginEncryptionUtil since the global server would return + // the skin too fast, that's why we upload it after we know for sure that the target server + // is ready to handle the result of the global server + connector.getSkinUploader().uploadSkin(certChainData, clientData); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 7a7069d07..aed98b09c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -34,10 +34,8 @@ import lombok.Getter; import org.geysermc.connector.skin.SkinProvider; import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.InputMode; -import org.geysermc.floodgate.util.RawSkin; import org.geysermc.floodgate.util.UiProfile; -import java.awt.image.BufferedImage; import java.util.Base64; import java.util.UUID; @@ -115,77 +113,12 @@ public final class BedrockClientData { @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; - private static RawSkin getLegacyImage(byte[] imageData, boolean alex) { - if (imageData == null) { - return null; - } - - // width * height * 4 (rgba) - switch (imageData.length) { - case 8192: - return new RawSkin(64, 32, imageData, alex); - case 16384: - return new RawSkin(64, 64, imageData, alex); - case 32768: - return new RawSkin(64, 128, imageData, alex); - case 65536: - return new RawSkin(128, 128, imageData, alex); - default: - throw new IllegalArgumentException("Unknown legacy skin size"); - } - } - public void setJsonData(JsonNode data) { if (this.jsonData == null && data != null) { this.jsonData = data; } } - /** - * Taken from https://github.com/NukkitX/Nukkit/blob/master/src/main/java/cn/nukkit/network/protocol/LoginPacket.java
- * Internally only used for Skins, but can be used for Capes too - */ - public RawSkin getImage(String name) { - if (jsonData == null || !jsonData.has(name + "Data")) { - return null; - } - - boolean alex = false; - if (name.equals("Skin")) { - alex = isAlex(); - } - - byte[] image = Base64.getDecoder().decode(jsonData.get(name + "Data").asText()); - if (jsonData.has(name + "ImageWidth") && jsonData.has(name + "ImageHeight")) { - return new RawSkin( - jsonData.get(name + "ImageWidth").asInt(), - jsonData.get(name + "ImageHeight").asInt(), - image, alex - ); - } - return getLegacyImage(image, alex); - } - - public RawSkin getAndTransformImage(String name) { - RawSkin skin = getImage(name); - if (skin != null && (skin.width > 64 || skin.height > 64)) { - BufferedImage scaledImage = - SkinProvider.imageDataToBufferedImage(skin.data, skin.width, skin.height); - - int max = Math.max(skin.width, skin.height); - while (max > 64) { - max /= 2; - scaledImage = SkinProvider.scale(scaledImage); - } - - byte[] skinData = SkinProvider.bufferedImageToImageData(scaledImage); - skin.width = scaledImage.getWidth(); - skin.height = scaledImage.getHeight(); - skin.data = skinData; - } - return skin; - } - public boolean isAlex() { try { byte[] bytes = Base64.getDecoder().decode(geometryName.getBytes(Charsets.UTF_8)); diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java new file mode 100644 index 000000000..e710a3591 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.skin; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Getter; +import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.utils.Constants; +import org.geysermc.connector.utils.PluginMessageUtils; +import org.geysermc.floodgate.util.WebsocketEventType; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; + +import static org.geysermc.connector.utils.PluginMessageUtils.getSkinChannel; + +public final class FloodgateSkinUploader { + private final ObjectMapper JACKSON = new ObjectMapper(); + private final List skinQueue = new ArrayList<>(); + + private final GeyserLogger logger; + private final WebSocketClient client; + + @Getter private int id; + @Getter private String verifyCode; + @Getter private int subscribersCount; + + public FloodgateSkinUploader(GeyserConnector connector) { + this.logger = connector.getLogger(); + this.client = new WebSocketClient(Constants.SKIN_UPLOAD_URI) { + @Override + public void onOpen(ServerHandshake handshake) { + setConnectionLostTimeout(11); + + Iterator queueIterator = skinQueue.iterator(); + while (isOpen() && queueIterator.hasNext()) { + send(queueIterator.next()); + queueIterator.remove(); + } + } + + @Override + public void onMessage(String message) { + System.out.println(message); + // The reason why I don't like Jackson + try { + JsonNode node = JACKSON.readTree(message); + if (node.has("error")) { + logger.error("Got an error: " + node.get("error").asText()); + return; + } + + int typeId = node.get("event_id").asInt(); + WebsocketEventType type = WebsocketEventType.getById(typeId); + if (type == null) { + logger.warning(String.format( + "Got (unknown) type %s. Ensure that Geyser is on the latest version and report this issue!", + typeId)); + return; + } + + switch (type) { + case SUBSCRIBER_CREATED: + id = node.get("id").asInt(); + verifyCode = node.get("verify_code").asText(); + break; + case SUBSCRIBERS_COUNT: + subscribersCount = node.get("subscribers_count").asInt(); + break; + case SKIN_UPLOADED: + // if Geyser is the only subscriber we have send it to the server manually + if (subscribersCount != 1) { + break; + } + + String xuid = node.get("xuid").asText(); + String value = node.get("value").asText(); + String signature = node.get("signature").asText(); + ; + + GeyserSession session = connector.getPlayerByXuid(xuid); + if (session != null) { + byte[] bytes = (value + '\0' + signature) + .getBytes(StandardCharsets.UTF_8); + PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes); + } + break; + } + } catch (Exception e) { + logger.error("Error while receiving a message", e); + } + } + + @Override + public void onClose(int code, String reason, boolean remote) { + if (reason != null && !reason.isEmpty()) { + // The reason why I don't like Jackson + try { + JsonNode node = JACKSON.readTree(reason); + // info means that the uploader itself did nothing wrong + if (node.has("info")) { + String info = node.get("info").asText(); + logger.debug("Got disconnected from the skin uploader: " + info); + } + // error means that the uploader did something wrong + if (node.has("error")) { + String error = node.get("error").asText(); + logger.info("Got disconnected from the skin uploader: " + error); + } + // it can't be something else then info or error, so we won't handle anything other than that. + // try to reconnect (which will make a new id and verify token) after a few seconds + reconnectLater(connector); + } catch (Exception e) { + logger.error("Error while handling onClose", e); + } + } + } + + @Override + public void onError(Exception ex) { + logger.error("Got an error", ex); + } + }; + } + + public void uploadSkin(JsonNode chainData, String clientData) { + if (chainData == null || !chainData.isArray() || clientData == null) { + return; + } + + ObjectNode node = JACKSON.createObjectNode(); + node.set("chain_data", chainData); + node.put("client_data", clientData); + + // The reason why I don't like Jackson + String jsonString; + try { + jsonString = JACKSON.writeValueAsString(node); + } catch (Exception e) { + logger.error("Failed to upload skin", e); + return; + } + + if (client.isOpen()) { + client.send(jsonString); + return; + } + skinQueue.add(jsonString); + } + + private void reconnectLater(GeyserConnector connector) { + long additionalTime = ThreadLocalRandom.current().nextInt(7); + connector.getGeneralThreadPool().schedule(() -> { + try { + if (!client.connectBlocking()) { + reconnectLater(connector); + } + } catch (InterruptedException ignored) { + reconnectLater(connector); + } + }, 8 + additionalTime, TimeUnit.SECONDS); + } + + public FloodgateSkinUploader start() { + client.connect(); + return this; + } + + public void stop() { + client.close(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/Constants.java b/connector/src/main/java/org/geysermc/connector/utils/Constants.java new file mode 100644 index 000000000..8dcd63d3e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/Constants.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.utils; + +import java.net.URI; +import java.net.URISyntaxException; + +public final class Constants { + public static final URI SKIN_UPLOAD_URI; + + static { + URI skinUploadUri = null; + try { + skinUploadUri = new URI("wss://api.geysermc.org/ws"); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + SKIN_UPLOAD_URI = skinUploadUri; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java index 00c7aea0f..f68ee0e4c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LoginEncryptionUtils.java @@ -118,7 +118,8 @@ public class LoginEncryptionUtils { session.setAuthenticationData(new AuthData( extraData.get("displayName").asText(), UUID.fromString(extraData.get("identity").asText()), - extraData.get("XUID").asText() + extraData.get("XUID").asText(), + certChainData, clientData )); if (payload.get("identityPublicKey").getNodeType() != JsonNodeType.STRING) { diff --git a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java index 4e0fd6074..1569002f9 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/PluginMessageUtils.java @@ -25,12 +25,15 @@ package org.geysermc.connector.utils; +import com.github.steveice10.mc.protocol.packet.ingame.server.ServerPluginMessagePacket; import com.google.common.base.Charsets; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.session.GeyserSession; import java.nio.ByteBuffer; public class PluginMessageUtils { + private static final String SKIN_CHANNEL = "floodgate:skin"; private static final byte[] GEYSER_BRAND_DATA; private static final byte[] FLOODGATE_REGISTER_DATA; @@ -42,7 +45,7 @@ public class PluginMessageUtils { .put(data) .array(); - FLOODGATE_REGISTER_DATA = "floodgate:skin\0floodgate:form".getBytes(Charsets.UTF_8); + FLOODGATE_REGISTER_DATA = (SKIN_CHANNEL + "\0floodgate:form").getBytes(Charsets.UTF_8); } /** @@ -63,6 +66,22 @@ public class PluginMessageUtils { return FLOODGATE_REGISTER_DATA; } + /** + * Returns the skin channel used in Floodgate + */ + public static String getSkinChannel() { + return SKIN_CHANNEL; + } + + public static void sendMessage(GeyserSession session, String channel, byte[] data) { + byte[] finalData = + ByteBuffer.allocate(data.length + getVarIntLength(data.length)) + .put(writeVarInt(data.length)) + .put(data) + .array(); + session.sendDownstreamPacket(new ServerPluginMessagePacket(channel, finalData)); + } + private static byte[] writeVarInt(int value) { byte[] data = new byte[getVarIntLength(value)]; int index = 0; From ec23e409b628273b95abcf62578b2b8d56f4fbdb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 13 Feb 2021 15:29:54 -0500 Subject: [PATCH 075/161] Use switch statements, and change default block usage This is to support future changes of using multiple block translators --- .../holder/BlockInventoryHolder.java | 9 ++++---- .../translators/AnvilInventoryTranslator.java | 16 +++++++------- .../BrewingInventoryTranslator.java | 18 ++++++++-------- .../GrindstoneInventoryTranslator.java | 16 +++++++------- .../translators/LoomInventoryTranslator.java | 21 +++++++++---------- .../SmithingInventoryTranslator.java | 16 +++++++------- .../StonecutterInventoryTranslator.java | 11 +++++----- .../chest/DoubleChestInventoryTranslator.java | 11 +++++----- 8 files changed, 59 insertions(+), 59 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index c6da2aeb2..2db4e86a4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -48,15 +48,14 @@ import java.util.Set; */ public class BlockInventoryHolder extends InventoryHolder { /** - * The default Bedrock block ID to use as a fake block + * The default Java block ID to translate as a fake block */ - private final int defaultBedrockBlockId; + private final int defaultJavaBlockState; private final ContainerType containerType; private final Set validBlocks; public BlockInventoryHolder(String javaBlockIdentifier, ContainerType containerType, String... validBlocks) { - int javaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier); - this.defaultBedrockBlockId = BlockTranslator.getBedrockBlockId(javaBlockState); + this.defaultJavaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier); this.containerType = containerType; if (validBlocks != null) { this.validBlocks = Sets.newHashSet(validBlocks); @@ -90,7 +89,7 @@ public class BlockInventoryHolder extends InventoryHolder { UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(position); - blockPacket.setRuntimeId(defaultBedrockBlockId); + blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(defaultJavaBlockState)); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); inventory.setHolderPosition(position); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java index 0d2b62153..854ccb772 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java @@ -103,14 +103,14 @@ public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { - if (slotInfoData.getContainer() == ContainerSlotType.ANVIL_INPUT) { - return 0; - } - if (slotInfoData.getContainer() == ContainerSlotType.ANVIL_MATERIAL) { - return 1; - } - if (slotInfoData.getContainer() == ContainerSlotType.ANVIL_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { - return 2; + switch (slotInfoData.getContainer()) { + case ANVIL_INPUT: + return 0; + case ANVIL_MATERIAL: + return 1; + case ANVIL_RESULT: + case CREATIVE_OUTPUT: + return 2; } return super.bedrockSlotToJava(slotInfoData); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java index b44350615..992a74511 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java @@ -71,7 +71,6 @@ public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { if (slotInfoData.getContainer() == ContainerSlotType.BREWING_INPUT) { // Ingredient - // TODO: This hasn't worked and then suddenly, it did. return 3; } if (slotInfoData.getContainer() == ContainerSlotType.BREWING_RESULT) { @@ -98,14 +97,15 @@ public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator @Override public BedrockContainerSlot javaSlotToBedrockContainer(int slot) { - if (slot == 0 || slot == 1 || slot == 2) { - return new BedrockContainerSlot(ContainerSlotType.BREWING_RESULT, javaSlotToBedrock(slot)); - } - if (slot == 3) { - return new BedrockContainerSlot(ContainerSlotType.BREWING_INPUT, 0); - } - if (slot == 4) { - return new BedrockContainerSlot(ContainerSlotType.BREWING_FUEL, 4); + switch (slot) { + case 0: + case 1: + case 2: + return new BedrockContainerSlot(ContainerSlotType.BREWING_RESULT, javaSlotToBedrock(slot)); + case 3: + return new BedrockContainerSlot(ContainerSlotType.BREWING_INPUT, 0); + case 4: + return new BedrockContainerSlot(ContainerSlotType.BREWING_INPUT, 0); } return super.javaSlotToBedrockContainer(slot); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java index 81f2df8b3..65364e147 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/GrindstoneInventoryTranslator.java @@ -38,14 +38,14 @@ public class GrindstoneInventoryTranslator extends AbstractBlockInventoryTransla @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { - if (slotInfoData.getContainer() == ContainerSlotType.GRINDSTONE_INPUT) { - return 0; - } - if (slotInfoData.getContainer() == ContainerSlotType.GRINDSTONE_ADDITIONAL) { - return 1; - } - if (slotInfoData.getContainer() == ContainerSlotType.GRINDSTONE_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { - return 2; + switch (slotInfoData.getContainer()) { + case GRINDSTONE_INPUT: + return 0; + case GRINDSTONE_ADDITIONAL: + return 1; + case GRINDSTONE_RESULT: + case CREATIVE_OUTPUT: + return 2; } return super.bedrockSlotToJava(slotInfoData); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java index 409cc4f15..27e7e0f94 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java @@ -172,17 +172,16 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { - if (slotInfoData.getContainer() == ContainerSlotType.LOOM_INPUT) { - return 0; - } - if (slotInfoData.getContainer() == ContainerSlotType.LOOM_DYE) { - return 1; - } - if (slotInfoData.getContainer() == ContainerSlotType.LOOM_MATERIAL) { - return 2; - } - if (slotInfoData.getContainer() == ContainerSlotType.LOOM_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { - return 3; + switch (slotInfoData.getContainer()) { + case LOOM_INPUT: + return 0; + case LOOM_DYE: + return 1; + case LOOM_MATERIAL: + return 2; + case LOOM_RESULT: + case CREATIVE_OUTPUT: + return 3; } return super.bedrockSlotToJava(slotInfoData); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java index dabe5c511..dbe04f68b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/SmithingInventoryTranslator.java @@ -38,14 +38,14 @@ public class SmithingInventoryTranslator extends AbstractBlockInventoryTranslato @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { - if (slotInfoData.getContainer() == ContainerSlotType.SMITHING_TABLE_INPUT) { - return 0; - } - if (slotInfoData.getContainer() == ContainerSlotType.SMITHING_TABLE_MATERIAL) { - return 1; - } - if (slotInfoData.getContainer() == ContainerSlotType.SMITHING_TABLE_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { - return 2; + switch (slotInfoData.getContainer()) { + case SMITHING_TABLE_INPUT: + return 0; + case SMITHING_TABLE_MATERIAL: + return 1; + case SMITHING_TABLE_RESULT: + case CREATIVE_OUTPUT: + return 2; } return super.bedrockSlotToJava(slotInfoData); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java index af742c136..78ef48849 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java @@ -91,11 +91,12 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { - if (slotInfoData.getContainer() == ContainerSlotType.STONECUTTER_INPUT) { - return 0; - } - if (slotInfoData.getContainer() == ContainerSlotType.STONECUTTER_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { - return 1; + switch (slotInfoData.getContainer()) { + case STONECUTTER_INPUT: + return 0; + case STONECUTTER_RESULT: + case CREATIVE_OUTPUT: + return 1; } return super.bedrockSlotToJava(slotInfoData); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java index d1e628fce..5a5c8209d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/DoubleChestInventoryTranslator.java @@ -42,12 +42,11 @@ import org.geysermc.connector.network.translators.world.block.DoubleChestValue; import org.geysermc.connector.network.translators.world.block.entity.DoubleChestBlockEntityTranslator; public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { - private final int defaultBedrockBlockId; + private final int defaultJavaBlockState; public DoubleChestInventoryTranslator(int size) { super(size, 54); - int javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); - this.defaultBedrockBlockId = BlockTranslator.getBedrockBlockId(javaBlockState); + this.defaultJavaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); } @Override @@ -84,10 +83,12 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { Vector3i position = session.getPlayerEntity().getPosition().toInt().add(Vector3i.UP); Vector3i pairPosition = position.add(Vector3i.UNIT_X); + int bedrockBlockId = BlockTranslator.getBedrockBlockId(defaultJavaBlockState); + UpdateBlockPacket blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(position); - blockPacket.setRuntimeId(defaultBedrockBlockId); + blockPacket.setRuntimeId(bedrockBlockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); @@ -107,7 +108,7 @@ public class DoubleChestInventoryTranslator extends ChestInventoryTranslator { blockPacket = new UpdateBlockPacket(); blockPacket.setDataLayer(0); blockPacket.setBlockPosition(pairPosition); - blockPacket.setRuntimeId(defaultBedrockBlockId); + blockPacket.setRuntimeId(bedrockBlockId); blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); session.sendUpstreamPacket(blockPacket); From f2550de6332af99b59e423ea540b50114d387391 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 15 Feb 2021 12:57:18 -0500 Subject: [PATCH 076/161] Ping/MOTD changes required for 1.16.210.59 (#1931) --- .../connector/network/ConnectorServerEventHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java index b4a0ddb69..70fe26357 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -69,10 +69,10 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { BedrockPong pong = new BedrockPong(); pong.setEdition("MCPE"); - pong.setGameType("Default"); + pong.setGameType("Survival"); // Can only be Survival or Creative as of 1.16.210.59 pong.setNintendoLimited(false); pong.setProtocolVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()); - pong.setVersion(null); // Server tries to connect either way and it looks better + pong.setVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); // Required to not be empty as of 1.16.210.59 pong.setIpv4Port(config.getBedrock().getPort()); if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { From 93a74d669df6fb34ece1828b91861c7e0c423034 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 15 Feb 2021 16:36:47 -0500 Subject: [PATCH 077/161] Skin and skull fixes (#1923) * Skin and skull fixes - Handle the occasional greater-than-128-px skin - Remove unused Jackson dependency - Update used Jackson dependency - Handle skin downloading on another thread * Other small touchups * Flush after rescaling --- connector/pom.xml | 8 +- .../geysermc/connector/GeyserConnector.java | 3 +- .../geysermc/connector/skin/SkinManager.java | 10 ++- .../geysermc/connector/skin/SkinProvider.java | 75 +++++++++---------- .../connector/skin/SkullSkinManager.java | 45 +++-------- 5 files changed, 58 insertions(+), 83 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 8703c3117..77da3e4f2 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -20,13 +20,7 @@ com.fasterxml.jackson.dataformat jackson-dataformat-yaml - 2.9.8 - compile - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.9.8 + 2.10.2 compile diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index f86e0b1e6..3494f8c20 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -77,7 +77,8 @@ public class GeyserConnector { .enable(JsonParser.Feature.IGNORE_UNDEFINED) .enable(JsonParser.Feature.ALLOW_COMMENTS) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES); + .enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES) + .enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES); public static final String NAME = "Geyser"; public static final String GIT_VERSION = "DEV"; // A fallback for running in IDEs diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java b/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java index ae3abc943..fb8336aca 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java @@ -33,6 +33,7 @@ import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; import lombok.AllArgsConstructor; import lombok.Getter; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.auth.BedrockClientData; @@ -163,7 +164,7 @@ public class SkinManager { geometry = SkinProvider.SkinGeometry.getEars(data.isAlex()); // Store the skin and geometry for the ears - SkinProvider.storeEarSkin(entity.getUuid(), skin); + SkinProvider.storeEarSkin(skin); SkinProvider.storeEarGeometry(entity.getUuid(), data.isAlex()); } } @@ -267,7 +268,10 @@ public class SkinManager { return new GameProfileData(skinUrl, capeUrl, isAlex); } catch (Exception exception) { - GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName() + ": " + exception.getMessage()); + GeyserConnector.getInstance().getLogger().debug("Something went wrong while processing skin for " + profile.getName()); + if (GeyserConnector.getInstance().getConfig().isDebugMode()) { + exception.printStackTrace(); + } return loadBedrockOrOfflineSkin(profile); } } @@ -282,7 +286,7 @@ public class SkinManager { String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl(); String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl(); - if ("steve".equals(skinUrl) || "alex".equals(skinUrl)) { + if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserConnector.getInstance().getAuthType() != AuthType.ONLINE) { GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(profile.getId()); if (session != null) { diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java index 3f236932a..c4d4bc486 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java @@ -79,13 +79,12 @@ public class SkinProvider { .build(); private static final Map> requestedCapes = new ConcurrentHashMap<>(); - public static final SkinGeometry EMPTY_GEOMETRY = SkinProvider.SkinGeometry.getLegacy(false); private static final Map cachedGeometry = new ConcurrentHashMap<>(); public static final boolean ALLOW_THIRD_PARTY_EARS = GeyserConnector.getInstance().getConfig().isAllowThirdPartyEars(); - public static String EARS_GEOMETRY; - public static String EARS_GEOMETRY_SLIM; - public static SkinGeometry SKULL_GEOMETRY; + public static final String EARS_GEOMETRY; + public static final String EARS_GEOMETRY_SLIM; + public static final SkinGeometry SKULL_GEOMETRY; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @@ -229,15 +228,15 @@ public class SkinProvider { return CompletableFuture.completedFuture(officialCape); } - public static CompletableFuture requestEars(String earsUrl, EarsProvider provider, boolean newThread, Skin skin) { + public static CompletableFuture requestEars(String earsUrl, boolean newThread, Skin skin) { if (earsUrl == null || earsUrl.isEmpty()) return CompletableFuture.completedFuture(skin); CompletableFuture future; if (newThread) { - future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl, provider), EXECUTOR_SERVICE) + future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl), EXECUTOR_SERVICE) .whenCompleteAsync((outSkin, throwable) -> { }); } else { - Skin ears = supplyEars(skin, earsUrl, provider); // blocking + Skin ears = supplyEars(skin, earsUrl); // blocking future = CompletableFuture.completedFuture(ears); } return future; @@ -255,7 +254,7 @@ public class SkinProvider { public static CompletableFuture requestUnofficialEars(Skin officialSkin, UUID playerId, String username, boolean newThread) { for (EarsProvider provider : EarsProvider.VALUES) { Skin skin1 = getOrDefault( - requestEars(provider.getUrlFor(playerId, username), provider, newThread, officialSkin), + requestEars(provider.getUrlFor(playerId, username), newThread, officialSkin), officialSkin, 4 ); if (skin1.isEars()) { @@ -295,12 +294,11 @@ public class SkinProvider { } /** - * Stores the ajusted skin with the ear texture to the cache + * Stores the adjusted skin with the ear texture to the cache * - * @param playerID The UUID to cache it against * @param skin The skin to cache */ - public static void storeEarSkin(UUID playerID, Skin skin) { + public static void storeEarSkin(Skin skin) { cachedSkins.put(skin.getTextureUrl(), skin); } @@ -324,7 +322,7 @@ public class SkinProvider { } private static Cape supplyCape(String capeUrl, CapeProvider provider) { - byte[] cape = new byte[0]; + byte[] cape = EMPTY_CAPE.getCapeData(); try { cape = requestImage(capeUrl, provider); } catch (Exception ignored) {} // just ignore I guess @@ -334,7 +332,7 @@ public class SkinProvider { return new Cape( capeUrl, urlSection[urlSection.length - 1], // get the texture id and use it as cape id - cape.length > 0 ? cape : EMPTY_CAPE.getCapeData(), + cape, System.currentTimeMillis(), cape.length == 0 ); @@ -345,10 +343,9 @@ public class SkinProvider { * * @param existingSkin The players current skin * @param earsUrl The URL to get the ears texture from - * @param provider The ears texture provider * @return The updated skin with ears */ - private static Skin supplyEars(Skin existingSkin, String earsUrl, EarsProvider provider) { + private static Skin supplyEars(Skin existingSkin, String earsUrl) { try { // Get the ears texture BufferedImage ears = ImageIO.read(new URL(earsUrl)); @@ -415,14 +412,15 @@ public class SkinProvider { // if the requested image is a cape if (provider != null) { - while(image.getWidth() > 64) { - image = scale(image); + if (image.getWidth() > 64) { + image = scale(image, 64, 32); + } + } else { + // Very rarely, skins can be larger than Minecraft's default. + // Bedrock will not render anything above a width of 128. + if (image.getWidth() > 128) { + image = scale(image, 128, image.getHeight() / (image.getWidth() / 128)); } - BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_ARGB); - Graphics g = newImage.createGraphics(); - g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null); - g.dispose(); - image = newImage; } byte[] data = bufferedImageToImageData(image); @@ -506,12 +504,13 @@ public class SkinProvider { return null; } - private static BufferedImage scale(BufferedImage bufferedImage) { - BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_ARGB); + private static BufferedImage scale(BufferedImage bufferedImage, int newWidth, int newHeight) { + BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = resized.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g2.drawImage(bufferedImage, 0, 0, bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, null); + g2.drawImage(bufferedImage, 0, 0, newWidth, newHeight, null); g2.dispose(); + bufferedImage.flush(); return resized; } @@ -579,17 +578,17 @@ public class SkinProvider { @AllArgsConstructor @Getter public static class SkinAndCape { - private Skin skin; - private Cape cape; + private final Skin skin; + private final Cape cape; } @AllArgsConstructor @Getter public static class Skin { private UUID skinOwner; - private String textureUrl; - private byte[] skinData; - private long requestedOn; + private final String textureUrl; + private final byte[] skinData; + private final long requestedOn; private boolean updated; private boolean ears; @@ -603,19 +602,19 @@ public class SkinProvider { @AllArgsConstructor @Getter public static class Cape { - private String textureUrl; - private String capeId; - private byte[] capeData; - private long requestedOn; - private boolean failed; + private final String textureUrl; + private final String capeId; + private final byte[] capeData; + private final long requestedOn; + private final boolean failed; } @AllArgsConstructor @Getter public static class SkinGeometry { - private String geometryName; - private String geometryData; - private boolean failed; + private final String geometryName; + private final String geometryData; + private final boolean failed; /** * Generate generic geometry diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java b/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java index 644323a42..562e2c50f 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkullSkinManager.java @@ -27,65 +27,42 @@ package org.geysermc.connector.skin; import com.nukkitx.protocol.bedrock.data.skin.ImageData; import com.nukkitx.protocol.bedrock.data.skin.SerializedSkin; -import com.nukkitx.protocol.bedrock.packet.PlayerListPacket; +import com.nukkitx.protocol.bedrock.packet.PlayerSkinPacket; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.utils.LanguageUtils; import java.util.Collections; -import java.util.UUID; import java.util.function.Consumer; public class SkullSkinManager extends SkinManager { - public static PlayerListPacket.Entry buildSkullEntryManually(UUID uuid, String username, long geyserId, - String skinId, byte[] skinData) { + public static SerializedSkin buildSkullEntryManually(String skinId, byte[] skinData) { // Prevents https://cdn.discordapp.com/attachments/613194828359925800/779458146191147008/unknown.png skinId = skinId + "_skull"; - SerializedSkin serializedSkin = SerializedSkin.of( + return SerializedSkin.of( skinId, SkinProvider.SKULL_GEOMETRY.getGeometryName(), ImageData.of(skinData), Collections.emptyList(), ImageData.of(SkinProvider.EMPTY_CAPE.getCapeData()), SkinProvider.SKULL_GEOMETRY.getGeometryData(), "", true, false, false, SkinProvider.EMPTY_CAPE.getCapeId(), skinId ); - - PlayerListPacket.Entry entry = new PlayerListPacket.Entry(uuid); - entry.setName(username); - entry.setEntityId(geyserId); - entry.setSkin(serializedSkin); - entry.setXuid(""); - entry.setPlatformChatId(""); - entry.setTeacher(false); - entry.setTrustedSkin(true); - return entry; } public static void requestAndHandleSkin(PlayerEntity entity, GeyserSession session, Consumer skinConsumer) { GameProfileData data = GameProfileData.from(entity.getProfile()); - SkinProvider.requestSkin(entity.getUuid(), data.getSkinUrl(), false) + SkinProvider.requestSkin(entity.getUuid(), data.getSkinUrl(), true) .whenCompleteAsync((skin, throwable) -> { try { if (session.getUpstream().isInitialized()) { - PlayerListPacket.Entry updatedEntry = buildSkullEntryManually( - entity.getUuid(), - entity.getUsername(), - entity.getGeyserId(), - skin.getTextureUrl(), - skin.getSkinData() - ); - - PlayerListPacket playerAddPacket = new PlayerListPacket(); - playerAddPacket.setAction(PlayerListPacket.Action.ADD); - playerAddPacket.getEntries().add(updatedEntry); - session.sendUpstreamPacket(playerAddPacket); - - // It's a skull. We don't want them in the player list. - PlayerListPacket playerRemovePacket = new PlayerListPacket(); - playerRemovePacket.setAction(PlayerListPacket.Action.REMOVE); - playerRemovePacket.getEntries().add(updatedEntry); - session.sendUpstreamPacket(playerRemovePacket); + PlayerSkinPacket packet = new PlayerSkinPacket(); + packet.setUuid(entity.getUuid()); + packet.setOldSkinName(""); + packet.setNewSkinName(skin.getTextureUrl()); + packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData())); + packet.setTrustedSkin(true); + session.sendUpstreamPacket(packet); } } catch (Exception e) { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); From 1f1d94aa811cf9b35677a6783a035bf6be0b02d7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 15 Feb 2021 21:41:11 -0500 Subject: [PATCH 078/161] Don't allow the sub-MOTD to be empty (#1933) As of 1.16.210.59, the sub-MOTD cannot be blank: https://bugs.mojang.com/browse/MCPE-117979 This workaround will be implemented now so the widest range of Geyser versions will properly show a ping in 1.16.210 - better an 'outdated server' message than 'unable to connect to world.' --- .../network/ConnectorServerEventHandler.java | 37 +++++++++++++------ connector/src/main/resources/config.yml | 5 ++- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java index 70fe26357..a1ebbc8d0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -42,6 +42,15 @@ import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; public class ConnectorServerEventHandler implements BedrockServerEventHandler { + /* + The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client + */ + private static final int MINECRAFT_VERSION_BYTES_LENGTH = BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion().getBytes(StandardCharsets.UTF_8).length; + private static final int BRAND_BYTES_LENGTH = GeyserConnector.NAME.getBytes(StandardCharsets.UTF_8).length; + /** + * The MOTD, sub-MOTD and Minecraft version ({@link #MINECRAFT_VERSION_BYTES_LENGTH}) combined cannot reach this length. + */ + private static final int MAGIC_RAKNET_LENGTH = 338; private final GeyserConnector connector; @@ -72,13 +81,13 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { pong.setGameType("Survival"); // Can only be Survival or Creative as of 1.16.210.59 pong.setNintendoLimited(false); pong.setProtocolVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getProtocolVersion()); - pong.setVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); // Required to not be empty as of 1.16.210.59 + pong.setVersion(BedrockProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()); // Required to not be empty as of 1.16.210.59. Can only contain . and numbers. pong.setIpv4Port(config.getBedrock().getPort()); if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) { String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n"); String mainMotd = motd[0]; // First line of the motd. - String subMotd = (motd.length != 1) ? motd[1] : ""; // Second line of the motd if present, otherwise blank. + String subMotd = (motd.length != 1) ? motd[1] : GeyserConnector.NAME; // Second line of the motd if present, otherwise default. pong.setMotd(mainMotd.trim()); pong.setSubMotd(subMotd.trim()); // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit. @@ -95,22 +104,28 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { pong.setMaximumPlayerCount(config.getMaxPlayers()); } - if (pong.getMotd() == null) { - pong.setMotd(""); + // Fallbacks to prevent errors and allow Bedrock to see the server + if (pong.getMotd() == null || pong.getMotd().trim().isEmpty()) { + pong.setMotd(GeyserConnector.NAME); } - if (pong.getSubMotd() == null) { - pong.setSubMotd(""); + if (pong.getSubMotd() == null || pong.getSubMotd().trim().isEmpty()) { + // Sub-MOTD cannot be empty as of 1.16.210.59 + pong.setSubMotd(GeyserConnector.NAME); } // The ping will not appear if the MOTD + sub-MOTD is of a certain length. // We don't know why, though byte[] motdArray = pong.getMotd().getBytes(StandardCharsets.UTF_8); - if (motdArray.length + pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length > 338) { - // Remove the sub-MOTD first since that only appears locally - pong.setSubMotd(""); - if (motdArray.length > 338) { + int subMotdLength = pong.getSubMotd().getBytes(StandardCharsets.UTF_8).length; + if (motdArray.length + subMotdLength > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH)) { + // Shorten the sub-MOTD first since that only appears locally + if (subMotdLength > BRAND_BYTES_LENGTH) { + pong.setSubMotd(GeyserConnector.NAME); + subMotdLength = BRAND_BYTES_LENGTH; + } + if (motdArray.length > (MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength)) { // If the top MOTD is still too long, we chop it down - byte[] newMotdArray = new byte[339]; + byte[] newMotdArray = new byte[MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength]; System.arraycopy(motdArray, 0, newMotdArray, 0, newMotdArray.length); pong.setMotd(new String(newMotdArray, StandardCharsets.UTF_8)); } diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index cdb2cb362..da46f5804 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -18,8 +18,9 @@ bedrock: # This option is for the plugin version only. clone-remote-port: false # The MOTD that will be broadcasted to Minecraft: Bedrock Edition clients. This is irrelevant if "passthrough-motd" is set to true - motd1: "GeyserMC" - motd2: "Another GeyserMC forced host." + # If either of these are empty, the respective string will default to "Geyser" + motd1: "Geyser" + motd2: "Another Geyser server." # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. server-name: "Geyser" remote: From c79979e3e383f611a5035c1a031be3b0be424ff8 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 16 Feb 2021 18:54:04 +0100 Subject: [PATCH 079/161] Added timestamp to BedrockData --- .../org/geysermc/floodgate/util/BedrockData.java | 16 +++++++++------- .../org/geysermc/floodgate/util/InputMode.java | 2 +- .../connector/skin/FloodgateSkinUploader.java | 3 +-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java index 4ea5ee78e..cbf49e126 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java +++ b/common/src/main/java/org/geysermc/floodgate/util/BedrockData.java @@ -26,8 +26,8 @@ package org.geysermc.floodgate.util; import lombok.AccessLevel; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; /** * This class contains the raw data send by Geyser to Floodgate or from Floodgate to Floodgate. This @@ -35,9 +35,9 @@ import lombok.Getter; * present in the API module of the Floodgate repo) */ @Getter -@AllArgsConstructor(access = AccessLevel.PRIVATE) +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) public final class BedrockData implements Cloneable { - public static final int EXPECTED_LENGTH = 12; + public static final int EXPECTED_LENGTH = 13; private final String version; private final String username; @@ -53,6 +53,7 @@ public final class BedrockData implements Cloneable { private final int subscribeId; private final String verifyCode; + private final long timestamp; private final int dataLength; public static BedrockData of(String version, String username, String xuid, int deviceOs, @@ -60,7 +61,8 @@ public final class BedrockData implements Cloneable { LinkedPlayer linkedPlayer, boolean fromProxy, int subscribeId, String verifyCode) { return new BedrockData(version, username, xuid, deviceOs, languageCode, inputMode, - uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, EXPECTED_LENGTH); + uiProfile, ip, linkedPlayer, fromProxy, subscribeId, verifyCode, + System.currentTimeMillis(), EXPECTED_LENGTH); } public static BedrockData of(String version, String username, String xuid, int deviceOs, @@ -81,12 +83,12 @@ public final class BedrockData implements Cloneable { return new BedrockData( split[0], split[1], split[2], Integer.parseInt(split[3]), split[4], Integer.parseInt(split[5]), Integer.parseInt(split[6]), split[7], linkedPlayer, - "1".equals(split[9]), Integer.parseInt(split[10]), split[11], split.length + "1".equals(split[9]), Integer.parseInt(split[10]), split[11], Long.parseLong(split[12]), split.length ); } private static BedrockData emptyData(int dataLength) { - return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, + return new BedrockData(null, null, null, -1, null, -1, -1, null, null, false, -1, null, -1, dataLength); } @@ -101,7 +103,7 @@ public final class BedrockData implements Cloneable { languageCode + '\0' + uiProfile + '\0' + inputMode + '\0' + ip + '\0' + (fromProxy ? 1 : 0) + '\0' + (linkedPlayer != null ? linkedPlayer.toString() : "null") + '\0' + - subscribeId + '\0' + verifyCode; + subscribeId + '\0' + verifyCode + '\0' + timestamp; } @Override diff --git a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java index 9664e18ae..d49d2ea84 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/InputMode.java +++ b/common/src/main/java/org/geysermc/floodgate/util/InputMode.java @@ -29,7 +29,7 @@ package org.geysermc.floodgate.util; public enum InputMode { UNKNOWN, KEYBOARD_MOUSE, - TOUCH, // I guess Touch? + TOUCH, CONTROLLER, VR; diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index e710a3591..991db08bc 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -74,7 +74,6 @@ public final class FloodgateSkinUploader { @Override public void onMessage(String message) { - System.out.println(message); // The reason why I don't like Jackson try { JsonNode node = JACKSON.readTree(message); @@ -109,7 +108,6 @@ public final class FloodgateSkinUploader { String xuid = node.get("xuid").asText(); String value = node.get("value").asText(); String signature = node.get("signature").asText(); - ; GeyserSession session = connector.getPlayerByXuid(xuid); if (session != null) { @@ -182,6 +180,7 @@ public final class FloodgateSkinUploader { } private void reconnectLater(GeyserConnector connector) { + //todo doesn't work long additionalTime = ThreadLocalRandom.current().nextInt(7); connector.getGeneralThreadPool().schedule(() -> { try { From e0bd5a62a7897f0f3c441efd8ce8b0ee8ca36361 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 16 Feb 2021 16:25:46 -0500 Subject: [PATCH 080/161] Fix up some SpotBugs suggestions (#1911) This is a manual go-through of some bugs SpotBugs pointed out. --- .../platform/standalone/LoopbackUtil.java | 2 +- .../connector/common/main/IGeyserMain.java | 6 +-- .../entity/living/monster/WitherEntity.java | 4 +- .../translators/EntityIdentifierRegistry.java | 2 +- .../translators/item/ItemTranslator.java | 5 +-- .../java/world/JavaUnloadChunkTranslator.java | 2 +- .../geysermc/connector/utils/FileUtils.java | 39 ++++++++++--------- .../connector/utils/LanguageUtils.java | 4 +- .../geysermc/connector/utils/LocaleUtils.java | 29 +++++++------- .../connector/utils/ResourcePack.java | 10 ++++- .../connector/utils/SettingsUtils.java | 2 +- .../geysermc/connector/utils/WebUtils.java | 20 +++++----- 12 files changed, 66 insertions(+), 59 deletions(-) diff --git a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java index 9c10234f3..7eeba84bd 100644 --- a/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java +++ b/bootstrap/standalone/src/main/java/org/geysermc/platform/standalone/LoopbackUtil.java @@ -55,7 +55,7 @@ public class LoopbackUtil { if (!result.contains("minecraftuwp")) { Files.write(Paths.get(System.getenv("temp") + "/loopback_minecraft.bat"), loopbackCommand.getBytes(), new OpenOption[0]); - process = Runtime.getRuntime().exec(startScript); + Runtime.getRuntime().exec(startScript); geyserLogger.info(ChatColor.AQUA + LanguageUtils.getLocaleStringLog("geyser.bootstrap.loopback.added")); } diff --git a/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java b/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java index 3f674d7fa..f91da11b5 100644 --- a/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java +++ b/connector/src/main/java/org/geysermc/connector/common/main/IGeyserMain.java @@ -52,7 +52,7 @@ public class IGeyserMain { * @return The formatted message */ private String createMessage() { - String message = ""; + StringBuilder message = new StringBuilder(); InputStream helpStream = IGeyserMain.class.getClassLoader().getResourceAsStream("languages/run-help/" + Locale.getDefault().toString() + ".txt"); @@ -68,10 +68,10 @@ public class IGeyserMain { line = line.replace("${plugin_type}", this.getPluginType()); line = line.replace("${plugin_folder}", this.getPluginFolder()); - message += line + "\n"; + message.append(line).append("\n"); } - return message; + return message.toString(); } /** diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java index 8dcce6a7f..e024b4e55 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/WitherEntity.java @@ -46,7 +46,7 @@ public class WitherEntity extends MonsterEntity { if (entityMetadata.getId() >= 15 && entityMetadata.getId() <= 17) { Entity entity = session.getEntityCache().getEntityByJavaId((int) entityMetadata.getValue()); - if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue()) { + if (entity == null && session.getPlayerEntity().getEntityId() == (int) entityMetadata.getValue()) { entity = session.getPlayerEntity(); } @@ -62,7 +62,7 @@ public class WitherEntity extends MonsterEntity { } else if (entityMetadata.getId() == 17) { metadata.put(EntityData.WITHER_TARGET_3, targetID); } else if (entityMetadata.getId() == 18) { - metadata.put(EntityData.WITHER_INVULNERABLE_TICKS, (int) entityMetadata.getValue()); + metadata.put(EntityData.WITHER_INVULNERABLE_TICKS, entityMetadata.getValue()); // Show the shield for the first few seconds of spawning (like Java) if ((int) entityMetadata.getValue() >= 165) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java index f4c0f9abc..fb6d5b93d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/EntityIdentifierRegistry.java @@ -38,7 +38,7 @@ import java.io.InputStream; */ public class EntityIdentifierRegistry { - public static NbtMap ENTITY_IDENTIFIERS; + public static final NbtMap ENTITY_IDENTIFIERS; private EntityIdentifierRegistry() { } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index 90acb781a..f63df2f7e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -303,9 +303,8 @@ public abstract class ItemTranslator { CompoundTag javaTag = new CompoundTag(name); Map javaValue = javaTag.getValue(); if (tag != null && !tag.isEmpty()) { - for (String str : tag.keySet()) { - Object bedrockTag = tag.get(str); - Tag translatedTag = translateToJavaNBT(str, bedrockTag); + for (Map.Entry entry : tag.entrySet()) { + Tag translatedTag = translateToJavaNBT(entry.getKey(), entry.getValue()); if (translatedTag == null) continue; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java index b768a2074..4e0f03bd1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java @@ -44,7 +44,7 @@ public class JavaUnloadChunkTranslator extends PacketTranslator iterator = session.getSkullCache().keySet().iterator(); while (iterator.hasNext()) { Vector3i position = iterator.next(); - if (Math.floor(position.getX() / 16) == packet.getX() && Math.floor(position.getZ() / 16) == packet.getZ()) { + if ((position.getX() >> 4) == packet.getX() && (position.getZ() >> 4) == packet.getZ()) { session.getSkullCache().get(position).despawnEntity(session); iterator.remove(); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java index d1dd6fd78..a96d29da2 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/FileUtils.java @@ -90,21 +90,22 @@ public class FileUtils { */ public static File fileOrCopiedFromResource(File file, String name, Function format) throws IOException { if (!file.exists()) { + //noinspection ResultOfMethodCallIgnored file.createNewFile(); - FileOutputStream fos = new FileOutputStream(file); - InputStream input = GeyserConnector.class.getResourceAsStream("/" + name); // resources need leading "/" prefix + try (FileOutputStream fos = new FileOutputStream(file)) { + try (InputStream input = GeyserConnector.class.getResourceAsStream("/" + name)) { // resources need leading "/" prefix + byte[] bytes = new byte[input.available()]; - byte[] bytes = new byte[input.available()]; + //noinspection ResultOfMethodCallIgnored + input.read(bytes); - input.read(bytes); + for(char c : format.apply(new String(bytes)).toCharArray()) { + fos.write(c); + } - for(char c : format.apply(new String(bytes)).toCharArray()) { - fos.write(c); + fos.flush(); + } } - - fos.flush(); - input.close(); - fos.close(); } return file; @@ -122,14 +123,13 @@ public class FileUtils { file.createNewFile(); } - FileOutputStream fos = new FileOutputStream(file); + try (FileOutputStream fos = new FileOutputStream(file)) { + for (char c : data) { + fos.write(c); + } - for (char c : data) { - fos.write(c); + fos.flush(); } - - fos.flush(); - fos.close(); } /** @@ -232,9 +232,10 @@ public class FileUtils { try { int size = stream.available(); byte[] bytes = new byte[size]; - BufferedInputStream buf = new BufferedInputStream(stream); - buf.read(bytes, 0, bytes.length); - buf.close(); + try (BufferedInputStream buf = new BufferedInputStream(stream)) { + //noinspection ResultOfMethodCallIgnored + buf.read(bytes, 0, bytes.length); + } return bytes; } catch (IOException e) { throw new RuntimeException("Error while trying to read input stream!"); diff --git a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java index 1a1f758d6..5284bbcff 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LanguageUtils.java @@ -67,8 +67,8 @@ public class LanguageUtils { // Load the locale if (localeStream != null) { Properties localeProp = new Properties(); - try { - localeProp.load(new InputStreamReader(localeStream, StandardCharsets.UTF_8)); + try (InputStreamReader reader = new InputStreamReader(localeStream, StandardCharsets.UTF_8)) { + localeProp.load(reader); } catch (Exception e) { throw new AssertionError(getLocaleStringLog("geyser.language.load_failed", locale), e); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index 15a52cf7f..dea05cca0 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -236,24 +236,23 @@ public class LocaleUtils { WebUtils.downloadFile(clientJarInfo.getUrl(), tmpFilePath.toString()); // Load in the JAR as a zip and extract the file - ZipFile localeJar = new ZipFile(tmpFilePath.toString()); - InputStream fileStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json")); - FileOutputStream outStream = new FileOutputStream(localeFile); + try (ZipFile localeJar = new ZipFile(tmpFilePath.toString())) { + try (InputStream fileStream = localeJar.getInputStream(localeJar.getEntry("assets/minecraft/lang/en_us.json"))) { + try (FileOutputStream outStream = new FileOutputStream(localeFile)) { - // Write the file to the locale dir - byte[] buf = new byte[fileStream.available()]; - int length; - while ((length = fileStream.read(buf)) != -1) { - outStream.write(buf, 0, length); + // Write the file to the locale dir + byte[] buf = new byte[fileStream.available()]; + int length; + while ((length = fileStream.read(buf)) != -1) { + outStream.write(buf, 0, length); + } + + // Flush all changes to disk and cleanup + outStream.flush(); + } + } } - // Flush all changes to disk and cleanup - outStream.flush(); - outStream.close(); - - fileStream.close(); - localeJar.close(); - // Store the latest jar hash FileUtils.writeFile(GeyserConnector.getInstance().getBootstrap().getConfigFolder().resolve("locales/en_us.hash").toString(), clientJarInfo.getSha1().toCharArray()); diff --git a/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java index bcb1ffd50..91d1b782e 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ResourcePack.java @@ -30,6 +30,8 @@ import org.geysermc.connector.GeyserConnector; import java.io.File; import java.util.HashMap; import java.util.Map; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** @@ -70,10 +72,12 @@ public class ResourcePack { pack.sha256 = FileUtils.calculateSHA256(file); + Stream stream = null; try { ZipFile zip = new ZipFile(file); - zip.stream().forEach((x) -> { + stream = zip.stream(); + stream.forEach((x) -> { if (x.getName().contains("manifest.json")) { try { ResourcePackManifest manifest = FileUtils.loadJson(zip.getInputStream(x), ResourcePackManifest.class); @@ -94,6 +98,10 @@ public class ResourcePack { } catch (Exception e) { GeyserConnector.getInstance().getLogger().error(LanguageUtils.getLocaleStringLog("geyser.resource_pack.broken", file.getName())); e.printStackTrace(); + } finally { + if (stream != null) { + stream.close(); + } } } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java index 77afda53d..c01378d00 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SettingsUtils.java @@ -147,7 +147,7 @@ public class SettingsUtils { } if (Boolean.class.equals(gamerule.getType())) { - Boolean value = settingsResponse.getToggleResponses().get(offset).booleanValue(); + boolean value = settingsResponse.getToggleResponses().get(offset); if (value != session.getConnector().getWorldManager().getGameRuleBool(session, gamerule)) { session.getConnector().getWorldManager().setGameRule(session, gamerule.getJavaID(), value); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java index 874fb0620..2d69c9adf 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java @@ -112,7 +112,7 @@ public class WebUtils { */ private static String connectionToString(HttpURLConnection con) throws IOException { // Send the request (we dont use this but its required for getErrorStream() to work) - int code = con.getResponseCode(); + con.getResponseCode(); // Read the error message if there is one if not just read normally InputStream inputStream = con.getErrorStream(); @@ -120,18 +120,18 @@ public class WebUtils { inputStream = con.getInputStream(); } - BufferedReader in = new BufferedReader(new InputStreamReader(inputStream)); - String inputLine; - StringBuffer content = new StringBuffer(); + StringBuilder content = new StringBuilder(); + try (BufferedReader in = new BufferedReader(new InputStreamReader(inputStream))) { + String inputLine; - while ((inputLine = in.readLine()) != null) { - content.append(inputLine); - content.append("\n"); + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + content.append("\n"); + } + + con.disconnect(); } - in.close(); - con.disconnect(); - return content.toString(); } } From 22c39d285e77c4a5e28c7066fc7068f2c5c18033 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 16 Feb 2021 19:09:57 -0500 Subject: [PATCH 081/161] Make BlockInventoryHolder block strings immutable; clean up lectern unloading --- .../inventory/holder/BlockInventoryHolder.java | 9 ++++++--- .../java/world/JavaUnloadChunkTranslator.java | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index 2db4e86a4..24be49144 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -26,7 +26,7 @@ package org.geysermc.connector.network.translators.inventory.holder; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.google.common.collect.Sets; +import com.google.common.collect.ImmutableSet; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; @@ -40,6 +40,7 @@ import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import java.util.Collections; +import java.util.HashSet; import java.util.Set; /** @@ -58,8 +59,10 @@ public class BlockInventoryHolder extends InventoryHolder { this.defaultJavaBlockState = BlockTranslator.getJavaBlockState(javaBlockIdentifier); this.containerType = containerType; if (validBlocks != null) { - this.validBlocks = Sets.newHashSet(validBlocks); - this.validBlocks.add(javaBlockIdentifier.split("\\[")[0]); + Set validBlocksTemp = new HashSet<>(validBlocks.length + 1); + Collections.addAll(validBlocksTemp, validBlocks); + validBlocksTemp.add(javaBlockIdentifier.split("\\[")[0]); + this.validBlocks = ImmutableSet.copyOf(validBlocksTemp); } else { this.validBlocks = Collections.singleton(javaBlockIdentifier.split("\\[")[0]); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java index 7e2ae9859..6d3efc1cb 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUnloadChunkTranslator.java @@ -54,7 +54,7 @@ public class JavaUnloadChunkTranslator extends PacketTranslator> 4) == packet.getX() && (position.getZ() >> 4) == packet.getZ()) { iterator.remove(); } } From 62cbdb8f5c41b78a332f3d6100857458d3c51166 Mon Sep 17 00:00:00 2001 From: YHDiamond <47502993+YHDiamond@users.noreply.github.com> Date: Tue, 16 Feb 2021 20:09:18 -0500 Subject: [PATCH 082/161] [ci skip] Fixed if spacing (#1941) Co-authored-by: yehudahrrs <47502993+yehudahrrs@users.noreply.github.com> --- .../java/org/geysermc/platform/sponge/GeyserSpongePlugin.java | 2 +- .../geysermc/connector/entity/living/animal/RabbitEntity.java | 2 +- .../connector/entity/living/merchant/VillagerEntity.java | 2 +- .../org/geysermc/connector/network/QueryPacketHandler.java | 4 ++-- .../translators/java/world/JavaPlaySoundTranslator.java | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java index 142d48d29..1986bbd22 100644 --- a/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java +++ b/bootstrap/sponge/src/main/java/org/geysermc/platform/sponge/GeyserSpongePlugin.java @@ -101,7 +101,7 @@ public class GeyserSpongePlugin implements GeyserBootstrap { } } - if (geyserConfig.getBedrock().isCloneRemotePort()){ + if (geyserConfig.getBedrock().isCloneRemotePort()) { geyserConfig.getBedrock().setPort(geyserConfig.getRemote().getPort()); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java index 544014115..752a0d106 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/RabbitEntity.java @@ -44,7 +44,7 @@ public class RabbitEntity extends AnimalEntity { if (entityMetadata.getId() == 15) { metadata.put(EntityData.SCALE, .55f); boolean isBaby = (boolean) entityMetadata.getValue(); - if(isBaby) { + if (isBaby) { metadata.put(EntityData.SCALE, .35f); metadata.getFlags().setFlag(EntityFlag.BABY, true); } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index d481cd0c5..56354774d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -125,7 +125,7 @@ public class VillagerEntity extends AbstractMerchantEntity { Pattern r = Pattern.compile("facing=([a-z]+)"); Matcher m = r.matcher(bedRotationZ); if (m.find()) { - switch (m.group(0)){ + switch (m.group(0)) { case "facing=south": //bed is facing south z = 180; diff --git a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java index 637f6d99d..87541f704 100644 --- a/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/QueryPacketHandler.java @@ -64,7 +64,7 @@ public class QueryPacketHandler { * @param buffer The Query data */ public QueryPacketHandler(GeyserConnector connector, InetSocketAddress sender, ByteBuf buffer) { - if(!isQueryPacket(buffer)) + if (!isQueryPacket(buffer)) return; this.connector = connector; @@ -225,7 +225,7 @@ public class QueryPacketHandler { query.write(new byte[] { 0x00, 0x00 }); // Fill player names - if(pingInfo != null) { + if (pingInfo != null) { for (String username : pingInfo.getPlayerList()) { query.write(username.getBytes()); query.write((byte) 0x00); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlaySoundTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlaySoundTranslator.java index 238e9ba32..56aa27992 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlaySoundTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaPlaySoundTranslator.java @@ -52,7 +52,7 @@ public class JavaPlaySoundTranslator extends PacketTranslator Date: Wed, 17 Feb 2021 13:23:55 +0800 Subject: [PATCH 083/161] Fix NPE in ServerEntityAnimationPacket (#1942) Closes: https://github.com/bundabrg/GeyserReversion/issues/41 Co-authored-by: bundabrg --- .../translators/java/entity/JavaEntityAnimationTranslator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java index 53c2864c8..735a5ea47 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityAnimationTranslator.java @@ -60,6 +60,9 @@ public class JavaEntityAnimationTranslator extends PacketTranslator Date: Wed, 17 Feb 2021 13:58:08 +0800 Subject: [PATCH 084/161] Fix NPE when a TitlePacket of type TIMES is sent (#1944) References: https://github.com/bundabrg/GeyserReversion/issues/29 Co-authored-by: bundabrg --- .../connector/network/translators/java/JavaTitleTranslator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java index d3b93068a..ffda57826 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaTitleTranslator.java @@ -71,6 +71,7 @@ public class JavaTitleTranslator extends PacketTranslator { titlePacket.setFadeInTime(packet.getFadeIn()); titlePacket.setFadeOutTime(packet.getFadeOut()); titlePacket.setStayTime(packet.getStay()); + titlePacket.setText(""); break; } From d9f8e9eec2a8a1c89c551a0e0a80e1c4625c3157 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 17 Feb 2021 18:00:00 -0500 Subject: [PATCH 085/161] Improve command freezing and fix up command suggestion translation (#1936) Merge commands with the same parameters (thanks Supreme) Add more suggestion types (blocks, items, entities) More optimizations Co-authored-by: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> Co-authored-by: rtm516 --- .../connector/entity/type/EntityType.java | 34 +- .../network/translators/item/Enchantment.java | 12 + .../translators/item/ItemRegistry.java | 11 + .../java/JavaDeclareCommandsTranslator.java | 298 ++++++++++++------ .../world/block/BlockTranslator.java | 7 + 5 files changed, 261 insertions(+), 101 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index e1e531f42..c45a38ad0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -30,8 +30,11 @@ import org.geysermc.connector.entity.*; import org.geysermc.connector.entity.living.*; import org.geysermc.connector.entity.living.animal.*; import org.geysermc.connector.entity.living.animal.horse.*; -import org.geysermc.connector.entity.living.animal.tameable.*; -import org.geysermc.connector.entity.living.merchant.*; +import org.geysermc.connector.entity.living.animal.tameable.CatEntity; +import org.geysermc.connector.entity.living.animal.tameable.ParrotEntity; +import org.geysermc.connector.entity.living.animal.tameable.WolfEntity; +import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity; +import org.geysermc.connector.entity.living.merchant.VillagerEntity; import org.geysermc.connector.entity.living.monster.*; import org.geysermc.connector.entity.living.monster.raid.AbstractIllagerEntity; import org.geysermc.connector.entity.living.monster.raid.PillagerEntity; @@ -39,6 +42,9 @@ import org.geysermc.connector.entity.living.monster.raid.RaidParticipantEntity; import org.geysermc.connector.entity.living.monster.raid.SpellcasterIllagerEntity; import org.geysermc.connector.entity.player.PlayerEntity; +import java.util.ArrayList; +import java.util.List; + @Getter public enum EntityType { @@ -174,17 +180,33 @@ public enum EntityType { */ ENDER_DRAGON_PART(EnderDragonPartEntity.class, 32, 0, 0, 0, 0, "minecraft:armor_stand"); + /** + * A list of all Java identifiers for use with command suggestions + */ + public static final String[] ALL_JAVA_IDENTIFIERS; private static final EntityType[] VALUES = values(); - private Class entityClass; + static { + List allJavaIdentifiers = new ArrayList<>(); + for (EntityType type : values()) { + if (type == AGENT || type == BALLOON || type == CHALKBOARD || type == NPC || type == TRIPOD_CAMERA || type == ENDER_DRAGON_PART) { + continue; + } + allJavaIdentifiers.add("minecraft:" + type.name().toLowerCase()); + } + ALL_JAVA_IDENTIFIERS = allJavaIdentifiers.toArray(new String[0]); + } + + private final Class entityClass; private final int type; private final float height; private final float width; private final float length; private final float offset; - private String identifier; + private final String identifier; EntityType(Class entityClass, int type, float height) { + //noinspection SuspiciousNameCombination this(entityClass, type, height, height); } @@ -198,8 +220,6 @@ public enum EntityType { EntityType(Class entityClass, int type, float height, float width, float length, float offset) { this(entityClass, type, height, width, length, offset, null); - - this.identifier = "minecraft:" + name().toLowerCase(); } EntityType(Class entityClass, int type, float height, float width, float length, float offset, String identifier) { @@ -209,7 +229,7 @@ public enum EntityType { this.width = width; this.length = length; this.offset = offset + 0.00001f; - this.identifier = identifier; + this.identifier = identifier == null ? "minecraft:" + name().toLowerCase() : identifier; } public static EntityType getFromIdentifier(String identifier) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java index 769cbd63a..a3b4b6c31 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java @@ -69,6 +69,18 @@ public enum Enchantment { QUICK_CHARGE, SOUL_SPEED; + /** + * A list of all enchantment Java identifiers for use with command suggestions. + */ + public static final String[] ALL_JAVA_IDENTIFIERS; + + static { + ALL_JAVA_IDENTIFIERS = new String[values().length]; + for (int i = 0; i < ALL_JAVA_IDENTIFIERS.length; i++) { + ALL_JAVA_IDENTIFIERS[i] = values()[i].javaIdentifier; + } + } + private final String javaIdentifier; Enchantment() { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java index e9b821588..9d1921731 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemRegistry.java @@ -63,6 +63,11 @@ public class ItemRegistry { public static final List ITEMS = new ArrayList<>(); public static final Int2ObjectMap ITEM_ENTRIES = new Int2ObjectOpenHashMap<>(); + /** + * A list of all Java item names. + */ + public static final String[] ITEM_NAMES; + /** * Bamboo item entry, used in PandaEntity.java */ @@ -116,6 +121,8 @@ public class ItemRegistry { // Used to get the Bedrock namespaced ID (in instances where there are small differences) Int2ObjectMap bedrockIdToIdentifier = new Int2ObjectOpenHashMap<>(); + List itemNames = new ArrayList<>(); + List itemEntries; try { itemEntries = GeyserConnector.JSON_MAPPER.readValue(stream, itemEntriesType); @@ -207,6 +214,8 @@ public class ItemRegistry { BUCKETS.add(entry.getValue().get("bedrock_id").intValue()); } + itemNames.add(entry.getKey()); + itemIndex++; } @@ -235,6 +244,8 @@ public class ItemRegistry { creativeItems.add(ItemData.fromNet(netId++, item.getId(), item.getDamage(), item.getCount(), item.getTag())); } CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]); + + ITEM_NAMES = itemNames.toArray(new String[0]); } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java index f6664c1a6..7de101811 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareCommandsTranslator.java @@ -33,21 +33,69 @@ import com.nukkitx.protocol.bedrock.data.command.CommandEnumData; import com.nukkitx.protocol.bedrock.data.command.CommandParamData; import com.nukkitx.protocol.bedrock.data.command.CommandParamType; import com.nukkitx.protocol.bedrock.packet.AvailableCommandsPacket; +import it.unimi.dsi.fastutil.Hash; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenCustomHashMap; import lombok.Getter; +import lombok.ToString; +import net.kyori.adventure.text.format.NamedTextColor; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.item.Enchantment; +import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import java.util.*; @Translator(packet = ServerDeclareCommandsPacket.class) public class JavaDeclareCommandsTranslator extends PacketTranslator { + + private static final String[] ENUM_BOOLEAN = {"true", "false"}; + private static final String[] VALID_COLORS; + private static final String[] VALID_SCOREBOARD_SLOTS; + + private static final Hash.Strategy PARAM_STRATEGY = new Hash.Strategy() { + @Override + public int hashCode(CommandParamData[][] o) { + return Arrays.deepHashCode(o); + } + + @Override + public boolean equals(CommandParamData[][] a, CommandParamData[][] b) { + if (a == b) return true; + if (a == null || b == null) return false; + if (a.length != b.length) return false; + for (int i = 0; i < a.length; i++) { + CommandParamData[] a1 = a[i]; + CommandParamData[] b1 = b[i]; + if (a1.length != b1.length) return false; + + for (int j = 0; j < a1.length; j++) { + if (!a1[j].equals(b1[j])) return false; + } + } + return true; + } + }; + + static { + List validColors = new ArrayList<>(NamedTextColor.NAMES.keys()); + validColors.add("reset"); + VALID_COLORS = validColors.toArray(new String[0]); + + List teamOptions = new ArrayList<>(Arrays.asList("list", "sidebar", "belowName")); + for (String color : NamedTextColor.NAMES.keys()) { + teamOptions.add("sidebar.team." + color); + } + VALID_SCOREBOARD_SLOTS = teamOptions.toArray(new String[0]); + } + @Override public void translate(ServerDeclareCommandsPacket packet, GeyserSession session) { // Don't send command suggestions if they are disabled @@ -60,48 +108,50 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator commandData = new ArrayList<>(); - Int2ObjectMap commands = new Int2ObjectOpenHashMap<>(); + IntSet commandNodes = new IntOpenHashSet(); + Set knownAliases = new HashSet<>(); + Map> commands = new Object2ObjectOpenCustomHashMap<>(PARAM_STRATEGY); Int2ObjectMap> commandArgs = new Int2ObjectOpenHashMap<>(); // Get the first node, it should be a root node - CommandNode rootNode = packet.getNodes()[packet.getFirstNodeIndex()]; + CommandNode rootNode = nodes[packet.getFirstNodeIndex()]; // Loop through the root nodes to get all commands for (int nodeIndex : rootNode.getChildIndices()) { - CommandNode node = packet.getNodes()[nodeIndex]; + CommandNode node = nodes[nodeIndex]; // Make sure we don't have duplicated commands (happens if there is more than 1 root node) - if (commands.containsKey(nodeIndex)) { continue; } - if (commands.containsValue(node.getName())) { continue; } + if (!commandNodes.add(nodeIndex) || !knownAliases.add(node.getName().toLowerCase())) continue; // Get and update the commandArgs list with the found arguments if (node.getChildIndices().length >= 1) { for (int childIndex : node.getChildIndices()) { - commandArgs.putIfAbsent(nodeIndex, new ArrayList<>()); - commandArgs.get(nodeIndex).add(packet.getNodes()[childIndex]); + commandArgs.computeIfAbsent(nodeIndex, ArrayList::new).add(nodes[childIndex]); } } - // Insert the command name into the list - commands.put(nodeIndex, node.getName()); + // Get and parse all params + CommandParamData[][] params = getParams(nodes[nodeIndex], nodes); + + // Insert the alias name into the command list + commands.computeIfAbsent(params, index -> new HashSet<>()).add(node.getName().toLowerCase()); } // The command flags, not sure what these do apart from break things List flags = Collections.emptyList(); // Loop through all the found commands - for (int commandID : commands.keySet()) { - String commandName = commands.get(commandID); + + for (Map.Entry> entry : commands.entrySet()) { + String commandName = entry.getValue().iterator().next(); // We know this has a value // Create a basic alias - CommandEnumData aliases = new CommandEnumData(commandName + "Aliases", new String[] { commandName.toLowerCase() }, false); - - // Get and parse all params - CommandParamData[][] params = getParams(packet.getNodes()[commandID], packet.getNodes()); + CommandEnumData aliases = new CommandEnumData(commandName + "Aliases", entry.getValue().toArray(new String[0]), false); // Build the completed command and add it to the final list - CommandData data = new CommandData(commandName, session.getConnector().getCommandManager().getDescription(commandName), flags, (byte) 0, aliases, params); + CommandData data = new CommandData(commandName, session.getConnector().getCommandManager().getDescription(commandName), flags, (byte) 0, aliases, entry.getKey()); commandData.add(data); } @@ -109,7 +159,7 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator treeData = rootParam.getTree(); - CommandParamData[][] params = new CommandParamData[treeData.size()][]; - // Fill the nested params array - int i = 0; - for (CommandParamData[] tree : treeData) { - params[i] = tree; - i++; - } - - return params; + return treeData.toArray(new CommandParamData[0][]); } return new CommandParamData[0][0]; @@ -155,14 +196,17 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator=", "==", etc + return CommandParamType.OPERATOR; + case BLOCK_STATE: - case BLOCK_PREDICATE: + return BlockTranslator.getAllBlockIdentifiers(); + case ITEM_STACK: - case ITEM_PREDICATE: - case COLOR: - case COMPONENT: - case OBJECTIVE: - case OBJECTIVE_CRITERIA: - case OPERATION: // Possibly OPERATOR - case PARTICLE: - case ROTATION: - case SCOREBOARD_SLOT: - case SCORE_HOLDER: - case SWIZZLE: - case TEAM: - case ITEM_SLOT: - case MOB_EFFECT: - case FUNCTION: - case ENTITY_ANCHOR: - case RANGE: - case FLOAT_RANGE: + return ItemRegistry.ITEM_NAMES; + case ITEM_ENCHANTMENT: + return Enchantment.ALL_JAVA_IDENTIFIERS; //TODO: inventory branch use Java enums + case ENTITY_SUMMON: - case DIMENSION: - case TIME: + return EntityType.ALL_JAVA_IDENTIFIERS; + + case COLOR: + return VALID_COLORS; + + case SCOREBOARD_SLOT: + return VALID_SCOREBOARD_SLOTS; + default: return CommandParamType.STRING; } } @Getter - private class ParamInfo { - private CommandNode paramNode; - private CommandParamData paramData; - private List children; + @ToString + private static class ParamInfo { + private final CommandNode paramNode; + private final CommandParamData paramData; + private final List children; /** * Create a new parameter info object @@ -252,33 +290,50 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator + // So if paramNode.getName() == "value" and enumData.getName() == "bool": + children.add(new ParamInfo(paramNode, new CommandParamData(paramNode.getName(), this.paramNode.isExecutable(), enumData, type, null, Collections.emptyList()))); } } @@ -288,6 +343,64 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator + * Take the gamerule command, and let's present three "subcommands" you can perform: + * + *

    + *
  • gamerule doDaylightCycle true
  • + *
  • gamerule announceAdvancements false
  • + *
  • gamerule randomTickSpeed 3
  • + *
+ * + * While all three of them are indeed part of the same command, the command setting randomTickSpeed parses an int, + * while the others use boolean. In Bedrock, this should be presented as a separate overload to indicate that this + * does something a little different. + *

+ * Therefore, this function will return true if the first two are compared, as they use the same + * parsers. If the third is compared with either of the others, this function will return false. + *

+ * Here's an example of how the above would be presented to Bedrock (as of 1.16.200). Notice how the top two CommandParamData + * classes of each array are identical in type, but the following class is different: + *

+         *     overloads=[
+         *         [
+         *            CommandParamData(name=doDaylightCycle, optional=false, enumData=CommandEnumData(name=announceAdvancements, values=[announceAdvancements, doDaylightCycle], isSoft=false), type=STRING, postfix=null, options=[])
+         *            CommandParamData(name=value, optional=false, enumData=CommandEnumData(name=value, values=[true, false], isSoft=false), type=null, postfix=null, options=[])
+         *         ]
+         *         [
+         *            CommandParamData(name=randomTickSpeed, optional=false, enumData=CommandEnumData(name=randomTickSpeed, values=[randomTickSpeed], isSoft=false), type=STRING, postfix=null, options=[])
+         *            CommandParamData(name=value, optional=false, enumData=null, type=INT, postfix=null, options=[])
+         *         ]
+         *     ]
+         * 
+ * + * @return if these two can be merged into one overload. + */ + private boolean isCompatible(CommandNode[] allNodes, CommandNode a, CommandNode b) { + if (a == b) return true; + if (a.getParser() != b.getParser()) return false; + if (a.getChildIndices().length != b.getChildIndices().length) return false; + + for (int i = 0; i < a.getChildIndices().length; i++) { + boolean hasSimilarity = false; + CommandNode a1 = allNodes[a.getChildIndices()[i]]; + // Search "b" until we find a child that matches this one + for (int j = 0; j < b.getChildIndices().length; j++) { + if (isCompatible(allNodes, a1, allNodes[b.getChildIndices()[j]])) { + hasSimilarity = true; + break; + } + } + + if (!hasSimilarity) { + return false; + } + } + return true; + } + /** * Get the tree of every parameter node (recursive) * @@ -301,13 +414,10 @@ public class JavaDeclareCommandsTranslator extends PacketTranslator childTree = child.getTree(); // Un-pack the tree append the child node to it and push into the list - for (CommandParamData[] subchild : childTree) { - CommandParamData[] tmpTree = new ArrayList() { - { - add(child.getParamData()); - addAll(Arrays.asList(subchild)); - } - }.toArray(new CommandParamData[0]); + for (CommandParamData[] subChild : childTree) { + CommandParamData[] tmpTree = new CommandParamData[subChild.length + 1]; + tmpTree[0] = child.getParamData(); + System.arraycopy(subChild, 0, tmpTree, 1, subChild.length); treeParamData.add(tmpTree); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index b047999e7..06d724728 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -386,4 +386,11 @@ public class BlockTranslator { } return itemIdentifier; } + + /** + * @return a list of all Java block identifiers. For use with command suggestions. + */ + public static String[] getAllBlockIdentifiers() { + return JAVA_ID_TO_JAVA_IDENTIFIER_MAP.values().toArray(new String[0]); + } } From 9208943ac643bea69d1e85dd6b535ce3ea70b8c9 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 17 Feb 2021 18:00:53 -0500 Subject: [PATCH 086/161] Bump Network and Netty dependencies (#1775) --- connector/pom.xml | 57 +++++++++++++++++-- .../network/session/GeyserSession.java | 9 ++- .../session/auth/BedrockClientData.java | 2 + .../player/BedrockActionTranslator.java | 3 +- .../geysermc/connector/skin/SkinManager.java | 2 +- .../connector/skin/SkullSkinManager.java | 2 +- 6 files changed, 65 insertions(+), 10 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 77da3e4f2..8b1bfe510 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -10,6 +10,10 @@ connector + + 4.1.59.Final + + org.geysermc @@ -26,14 +30,13 @@ com.github.CloudburstMC.Protocol bedrock-v422 - d41b84e86c + 294e7e5 compile net.sf.trove4j trove - com.nukkitx.network raknet @@ -41,10 +44,16 @@ - com.nukkitx.network + com.github.CloudburstMC.Network raknet - 1.6.20 + a94d2dd compile + + + io.netty + * + + com.nukkitx.fastutil @@ -147,15 +156,51 @@ io.netty netty-resolver-dns - 4.1.43.Final + ${netty.version} compile + + io.netty + netty-resolver-dns-native-macos + ${netty.version} + compile + osx-x86_64 + io.netty netty-codec-haproxy - 4.1.56.Final + ${netty.version} compile + + + io.netty + netty-handler + ${netty.version} + compile + + + io.netty + netty-transport-native-epoll + ${netty.version} + compile + linux-x86_64 + + + io.netty + netty-transport-native-epoll + ${netty.version} + compile + linux-aarch_64 + + + io.netty + netty-transport-native-kqueue + ${netty.version} + compile + osx-x86_64 + + org.reflections reflections diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 2f15cab75..999a2a50a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -839,7 +839,14 @@ public class GeyserSession implements CommandSender { startGamePacket.setMultiplayerCorrelationId(""); startGamePacket.setItemEntries(ItemRegistry.ITEMS); startGamePacket.setVanillaVersion("*"); - startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); + startGamePacket.setAuthoritativeMovementMode(AuthoritativeMovementMode.CLIENT); // can be removed once 1.16.200 support is dropped + + SyncedPlayerMovementSettings settings = new SyncedPlayerMovementSettings(); + settings.setMovementMode(AuthoritativeMovementMode.CLIENT); + settings.setRewindHistorySize(0); + settings.setServerAuthoritativeBlockBreaking(false); + startGamePacket.setPlayerMovementSettings(settings); + upstream.sendPacket(startGamePacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java index 10075a9a4..16e06c066 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/auth/BedrockClientData.java @@ -103,6 +103,8 @@ public class BedrockClientData { private String skinColor; @JsonProperty(value = "ThirdPartyNameOnly") private boolean thirdPartyNameOnly; + @JsonProperty(value = "PlayFabId") + private String playFabId; public enum UIProfile { @JsonEnumDefaultValue diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java index 789df85c2..c248b57a5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java @@ -37,6 +37,7 @@ import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlaye import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; +import com.nukkitx.protocol.bedrock.data.PlayerActionType; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; @@ -64,7 +65,7 @@ public class BedrockActionTranslator extends PacketTranslator Date: Thu, 18 Feb 2021 01:25:41 +0200 Subject: [PATCH 087/161] HAProxy PROXY protocol support for upstream connections (#1713) * Ignore unknown properties on configuration subclasses * Implement upstream PROXY protocol support --- .../geysermc/connector/GeyserConnector.java | 9 +- .../configuration/GeyserConfiguration.java | 11 +++ .../GeyserJacksonConfiguration.java | 31 ++++++ .../connector/network/CIDRMatcher.java | 95 +++++++++++++++++++ .../network/ConnectorServerEventHandler.java | 16 ++++ .../network/session/GeyserSession.java | 6 +- .../network/session/UpstreamSession.java | 2 +- connector/src/main/resources/config.yml | 8 ++ 8 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/CIDRMatcher.java diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 3494f8c20..0b7f84646 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.nukkitx.network.raknet.RakNetConstants; +import com.nukkitx.network.util.EventLoops; import com.nukkitx.protocol.bedrock.BedrockServer; import lombok.Getter; import lombok.Setter; @@ -196,7 +197,13 @@ public class GeyserConnector { RakNetConstants.MAXIMUM_MTU_SIZE = (short) config.getMtu(); logger.debug("Setting MTU to " + config.getMtu()); - bedrockServer = new BedrockServer(new InetSocketAddress(config.getBedrock().getAddress(), config.getBedrock().getPort())); + boolean enableProxyProtocol = config.getBedrock().isEnableProxyProtocol(); + bedrockServer = new BedrockServer( + new InetSocketAddress(config.getBedrock().getAddress(), config.getBedrock().getPort()), + 1, + EventLoops.commonGroup(), + enableProxyProtocol + ); bedrockServer.setHandler(new ConnectorServerEventHandler(this)); bedrockServer.bind().whenComplete((avoid, throwable) -> { if (throwable == null) { diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java index 31bcbe995..6052bd283 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserConfiguration.java @@ -27,9 +27,11 @@ package org.geysermc.connector.configuration; import com.fasterxml.jackson.annotation.JsonIgnore; import org.geysermc.connector.GeyserLogger; +import org.geysermc.connector.network.CIDRMatcher; import org.geysermc.connector.utils.LanguageUtils; import java.nio.file.Path; +import java.util.List; import java.util.Map; public interface GeyserConfiguration { @@ -106,6 +108,15 @@ public interface GeyserConfiguration { String getMotd2(); String getServerName(); + + boolean isEnableProxyProtocol(); + + List getProxyProtocolWhitelistedIPs(); + + /** + * @return Unmodifiable list of {@link CIDRMatcher}s from {@link #getProxyProtocolWhitelistedIPs()} + */ + List getWhitelistedIPsMatchers(); } interface IRemoteConfiguration { diff --git a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java index 4e03da52f..70aa3ff5d 100644 --- a/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java +++ b/connector/src/main/java/org/geysermc/connector/configuration/GeyserJacksonConfiguration.java @@ -25,16 +25,21 @@ package org.geysermc.connector.configuration; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.common.serializer.AsteriskSerializer; +import org.geysermc.connector.network.CIDRMatcher; import java.nio.file.Path; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; @Getter @JsonIgnoreProperties(ignoreUnknown = true) @@ -122,6 +127,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration private MetricsInfo metrics = new MetricsInfo(); @Getter + @JsonIgnoreProperties(ignoreUnknown = true) public static class BedrockConfiguration implements IBedrockConfiguration { @AsteriskSerializer.Asterisk(sensitive = true) private String address = "0.0.0.0"; @@ -137,9 +143,33 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("server-name") private String serverName = GeyserConnector.NAME; + + @JsonProperty("enable-proxy-protocol") + private boolean enableProxyProtocol = false; + + @JsonProperty("proxy-protocol-whitelisted-ips") + private List proxyProtocolWhitelistedIPs = Collections.emptyList(); + + @JsonIgnore + private List whitelistedIPsMatchers = null; + + @Override + public List getWhitelistedIPsMatchers() { + // Effective Java, Third Edition; Item 83: Use lazy initialization judiciously + List matchers = this.whitelistedIPsMatchers; + if (matchers == null) { + synchronized (this) { + this.whitelistedIPsMatchers = matchers = proxyProtocolWhitelistedIPs.stream() + .map(CIDRMatcher::new) + .collect(Collectors.toList()); + } + } + return Collections.unmodifiableList(matchers); + } } @Getter + @JsonIgnoreProperties(ignoreUnknown = true) public static class RemoteConfiguration implements IRemoteConfiguration { @Setter @AsteriskSerializer.Asterisk(sensitive = true) @@ -173,6 +203,7 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration } @Getter + @JsonIgnoreProperties(ignoreUnknown = true) public static class MetricsInfo implements IMetricsInfo { private boolean enabled = true; diff --git a/connector/src/main/java/org/geysermc/connector/network/CIDRMatcher.java b/connector/src/main/java/org/geysermc/connector/network/CIDRMatcher.java new file mode 100644 index 000000000..57e58ecc2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/CIDRMatcher.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/* + * Taken & modified from TCPShield, licensed under MIT. See https://github.com/TCPShield/RealIP/blob/master/LICENSE + * + * https://github.com/TCPShield/RealIP/blob/32d422a9523cb6e25b571072851f3306bb8bbc4f/src/main/java/net/tcpshield/tcpshield/validation/cidr/CIDRMatcher.java + */ +public class CIDRMatcher { + private final int maskBits; + private final int maskBytes; + private final boolean simpleCIDR; + private final InetAddress cidrAddress; + + public CIDRMatcher(String ipAddress) { + String[] split = ipAddress.split("/", 2); + + String parsedIPAddress; + if (split.length == 2) { + parsedIPAddress = split[0]; + + this.maskBits = Integer.parseInt(split[1]); + this.simpleCIDR = maskBits == 32; + } else { + parsedIPAddress = ipAddress; + + this.maskBits = -1; + this.simpleCIDR = true; + } + + this.maskBytes = simpleCIDR ? -1 : maskBits / 8; + + try { + cidrAddress = InetAddress.getByName(parsedIPAddress); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + public boolean matches(InetAddress inetAddress) { + // check if IP is IPv4 or IPv6 + if (cidrAddress.getClass() != inetAddress.getClass()) { + return false; + } + + // check for equality if it's a simple CIDR + if (simpleCIDR) { + return inetAddress.equals(cidrAddress); + } + + byte[] inetAddressBytes = inetAddress.getAddress(); + byte[] requiredAddressBytes = cidrAddress.getAddress(); + + byte finalByte = (byte) (0xFF00 >> (maskBits & 0x07)); + + for (int i = 0; i < maskBytes; i++) { + if (inetAddressBytes[i] != requiredAddressBytes[i]) { + return false; + } + } + + if (finalByte != 0) { + return (inetAddressBytes[maskBytes] & finalByte) == (requiredAddressBytes[maskBytes] & finalByte); + } + + return true; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java index a1ebbc8d0..bd5030a8b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/ConnectorServerEventHandler.java @@ -40,6 +40,7 @@ import org.geysermc.connector.utils.LanguageUtils; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; +import java.util.List; public class ConnectorServerEventHandler implements BedrockServerEventHandler { /* @@ -60,6 +61,21 @@ public class ConnectorServerEventHandler implements BedrockServerEventHandler { @Override public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) { + List allowedProxyIPs = connector.getConfig().getBedrock().getProxyProtocolWhitelistedIPs(); + if (connector.getConfig().getBedrock().isEnableProxyProtocol() && !allowedProxyIPs.isEmpty()) { + boolean isWhitelistedIP = false; + for (CIDRMatcher matcher : connector.getConfig().getBedrock().getWhitelistedIPsMatchers()) { + if (matcher.matches(inetSocketAddress.getAddress())) { + isWhitelistedIP = true; + break; + } + } + + if (!isWhitelistedIP) { + return false; + } + } + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.attempt_connect", inetSocketAddress)); return true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 999a2a50a..ac872d909 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -93,6 +93,7 @@ import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.EncryptionUtil; import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; @@ -378,7 +379,8 @@ public class GeyserSession implements CommandSender { tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes())); bedrockServerSession.addDisconnectHandler(disconnectReason -> { - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", bedrockServerSession.getAddress().getAddress(), disconnectReason)); + InetAddress address = bedrockServerSession.getRealAddress().getAddress(); + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.disconnect", address, disconnectReason)); disconnect(disconnectReason.name()); connector.removePlayer(this); @@ -588,7 +590,7 @@ public class GeyserSession implements CommandSender { clientData.getDeviceOS().ordinal(), clientData.getLanguageCode(), clientData.getCurrentInputMode().ordinal(), - upstream.getSession().getAddress().getAddress().getHostAddress() + upstream.getAddress().getAddress().getHostAddress() )); } catch (Exception e) { connector.getLogger().error(LanguageUtils.getLocaleStringLog("geyser.auth.floodgate.encrypt_fail"), e); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java b/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java index 04e208af3..f973574b0 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/UpstreamSession.java @@ -61,6 +61,6 @@ public class UpstreamSession { } public InetSocketAddress getAddress() { - return session.getAddress(); + return session.getRealAddress(); } } diff --git a/connector/src/main/resources/config.yml b/connector/src/main/resources/config.yml index da46f5804..ce202a3c1 100644 --- a/connector/src/main/resources/config.yml +++ b/connector/src/main/resources/config.yml @@ -23,6 +23,14 @@ bedrock: motd2: "Another Geyser server." # The Server Name that will be sent to Minecraft: Bedrock Edition clients. This is visible in both the pause menu and the settings menu. server-name: "Geyser" + # Whether to enable PROXY protocol or not for clients. You DO NOT WANT this feature unless you run UDP reverse proxy + # in front of your Geyser instance. + enable-proxy-protocol: false + # A list of allowed PROXY protocol speaking proxy IP addresses/subnets. Only effective when "enable-proxy-protocol" is enabled, and + # should really only be used when you are not able to use a proper firewall (usually true with shared hosting providers etc.). + # Keeping this list empty means there is no IP address whitelist. + # Both IP addresses and subnets are supported. + #proxy-protocol-whitelisted-ips: [ "127.0.0.1", "172.18.0.0/16" ] remote: # The IP address of the remote (Java Edition) server # If it is "auto", for standalone version the remote address will be set to 127.0.0.1, From 605201afc0d2b9af1c6b889015a225213e0138c3 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 17 Feb 2021 19:39:10 -0500 Subject: [PATCH 088/161] Make changes required for Protocol 2.6.2 --- .../inventory/InventoryTranslator.java | 26 +++++++++---------- .../translators/AnvilInventoryTranslator.java | 8 ++---- .../BeaconInventoryTranslator.java | 4 +-- .../EnchantingInventoryTranslator.java | 8 ++---- .../translators/LoomInventoryTranslator.java | 4 +-- .../PlayerInventoryTranslator.java | 11 ++------ .../StonecutterInventoryTranslator.java | 4 +-- 7 files changed, 25 insertions(+), 40 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 7fd24080e..7a9b890b3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -33,9 +33,9 @@ import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; -import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import it.unimi.dsi.fastutil.ints.*; import lombok.AllArgsConstructor; @@ -141,14 +141,14 @@ public abstract class InventoryTranslator { /** * If {@link #shouldHandleRequestFirst(StackRequestActionData, Inventory)} returns true, this will be called */ - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { return null; } - public void translateRequests(GeyserSession session, Inventory inventory, List requests) { + public void translateRequests(GeyserSession session, Inventory inventory, List requests) { boolean refresh = false; ItemStackResponsePacket responsePacket = new ItemStackResponsePacket(); - for (ItemStackRequestPacket.Request request : requests) { + for (ItemStackRequest request : requests) { ItemStackResponsePacket.Response response; if (request.getActions().length > 0) { StackRequestActionData firstAction = request.getActions()[0]; @@ -181,7 +181,7 @@ public abstract class InventoryTranslator { } } - public ItemStackResponsePacket.Response translateRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + public ItemStackResponsePacket.Response translateRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { ClickPlan plan = new ClickPlan(session, this, inventory); IntSet affectedSlots = new IntOpenHashSet(); for (StackRequestActionData action : request.getActions()) { @@ -380,7 +380,7 @@ public abstract class InventoryTranslator { return acceptRequest(request, makeContainerEntries(session, inventory, affectedSlots)); } - public ItemStackResponsePacket.Response translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + public ItemStackResponsePacket.Response translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { int recipeId = 0; int resultSize = 0; int timesCrafted = 0; @@ -485,7 +485,7 @@ public abstract class InventoryTranslator { return acceptRequest(request, makeContainerEntries(session, inventory, plan.getAffectedSlots())); } - public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + public ItemStackResponsePacket.Response translateAutoCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { int gridSize; int gridDimensions; if (this instanceof PlayerInventoryTranslator) { @@ -681,7 +681,7 @@ public abstract class InventoryTranslator { return acceptRequest(request, makeContainerEntries(session, inventory, plan.getAffectedSlots())); } - public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { // Handled in PlayerInventoryTranslator return rejectRequest(request); } @@ -717,15 +717,15 @@ public abstract class InventoryTranslator { } } - public static ItemStackResponsePacket.Response acceptRequest(ItemStackRequestPacket.Request request, List containerEntries) { + public static ItemStackResponsePacket.Response acceptRequest(ItemStackRequest request, List containerEntries) { return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.OK, request.getRequestId(), containerEntries); } - public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequestPacket.Request request) { + public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request) { return rejectRequest(request, true); } - public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequestPacket.Request request, boolean throwError) { + public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request, boolean throwError) { if (throwError) { // Currently for debugging, but might be worth it to keep in the future if something goes terribly wrong. new Throwable("DEBUGGING: ItemStackRequest rejected " + request.toString()).printStackTrace(); @@ -811,9 +811,9 @@ public abstract class InventoryTranslator { public static ItemStackResponsePacket.ItemEntry makeItemEntry(int bedrockSlot, GeyserItemStack itemStack) { ItemStackResponsePacket.ItemEntry itemEntry; if (!itemStack.isEmpty()) { - itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) itemStack.getAmount(), itemStack.getNetId(), ""); + itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) itemStack.getAmount(), itemStack.getNetId(), "", 0); } else { - itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) 0, 0, ""); + itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) 0, 0, "", 0); } return itemEntry; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java index 854ccb772..ded198ef6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AnvilInventoryTranslator.java @@ -28,14 +28,10 @@ package org.geysermc.connector.network.translators.inventory.translators; import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket; import com.nukkitx.nbt.NbtMap; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.ItemData; -import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.*; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; -import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import org.geysermc.connector.inventory.AnvilContainer; import org.geysermc.connector.inventory.GeyserItemStack; @@ -61,7 +57,7 @@ public class AnvilInventoryTranslator extends AbstractBlockInventoryTranslator { @Override @Deprecated - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { if (!(request.getActions()[1] instanceof CraftResultsDeprecatedStackRequestActionData)) { // Just silently log an error session.getConnector().getLogger().debug("Something isn't quite right with taking an item out of an anvil."); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java index d0db8f3d6..b0cb77151 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java @@ -32,12 +32,12 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.BeaconPaymentStackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; -import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import org.geysermc.connector.inventory.BeaconContainer; import org.geysermc.connector.inventory.Inventory; @@ -93,7 +93,7 @@ public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator } @Override - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { // Input a beacon payment BeaconPaymentStackRequestActionData beaconPayment = (BeaconPaymentStackRequestActionData) request.getActions()[0]; ClientSetBeaconEffectPacket packet = new ClientSetBeaconEffectPacket(beaconPayment.getPrimaryEffect(), beaconPayment.getSecondaryEffect()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java index 8d2f20145..3a25c2a89 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/EnchantingInventoryTranslator.java @@ -27,14 +27,10 @@ package org.geysermc.connector.network.translators.inventory.translators; import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; -import com.nukkitx.protocol.bedrock.data.inventory.EnchantOptionData; -import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; +import com.nukkitx.protocol.bedrock.data.inventory.*; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftRecipeStackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; -import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import com.nukkitx.protocol.bedrock.packet.PlayerEnchantOptionsPacket; import org.geysermc.connector.inventory.EnchantingContainer; @@ -109,7 +105,7 @@ public class EnchantingInventoryTranslator extends AbstractBlockInventoryTransla } @Override - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { // Client has requested an item to be enchanted CraftRecipeStackRequestActionData craftRecipeData = (CraftRecipeStackRequestActionData) request.getActions()[0]; EnchantingContainer enchantingInventory = (EnchantingContainer) inventory; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java index 27e7e0f94..3209a161e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java @@ -32,11 +32,11 @@ import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; -import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; @@ -120,7 +120,7 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { } @Override - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { // TODO: I anticipate this will be changed in the future to use something non-deprecated. Keep an eye out. StackRequestActionData data = request.getActions()[1]; if (!(data instanceof CraftResultsDeprecatedStackRequestActionData)) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java index 288f5926e..50a0ae2c7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java @@ -33,7 +33,6 @@ import com.nukkitx.protocol.bedrock.data.inventory.*; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; @@ -51,7 +50,6 @@ import org.geysermc.connector.utils.LanguageUtils; import java.util.Arrays; import java.util.Collections; -import java.util.List; public class PlayerInventoryTranslator extends InventoryTranslator { private static final ItemData UNUSUABLE_CRAFTING_SPACE_BLOCK = InventoryUtils.createUnusableSpaceBlock(LanguageUtils.getLocaleStringLog("geyser.inventory.unusable_item.creative")); @@ -211,12 +209,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { } @Override - public void translateRequests(GeyserSession session, Inventory inventory, List requests) { - super.translateRequests(session, inventory, requests); - } - - @Override - public ItemStackResponsePacket.Response translateRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + public ItemStackResponsePacket.Response translateRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { if (session.getGameMode() != GameMode.CREATIVE) { return super.translateRequest(session, inventory, request); } @@ -377,7 +370,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { } @Override - public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { ItemStack javaCreativeItem = null; IntSet affectedSlots = new IntOpenHashSet(); CraftState craftState = CraftState.START; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java index 78ef48849..3c267e121 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java @@ -30,11 +30,11 @@ import com.github.steveice10.mc.protocol.data.game.window.WindowType; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientClickWindowButtonPacket; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.CraftResultsDeprecatedStackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionData; import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.StackRequestActionType; -import com.nukkitx.protocol.bedrock.packet.ItemStackRequestPacket; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import it.unimi.dsi.fastutil.ints.IntList; import org.geysermc.connector.inventory.GeyserItemStack; @@ -58,7 +58,7 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl } @Override - public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequestPacket.Request request) { + public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { // TODO: Also surely to change in the future StackRequestActionData data = request.getActions()[1]; if (!(data instanceof CraftResultsDeprecatedStackRequestActionData)) { From b7828267a513c1f030327494a90265613f9dacfc Mon Sep 17 00:00:00 2001 From: David Choo Date: Fri, 19 Feb 2021 11:12:36 -0500 Subject: [PATCH 089/161] Projectile Movement (#1929) Fixes #1780 #1778 The laggy fireballs Llama spit Shulker bullets --- .../connector/entity/AbstractArrowEntity.java | 18 ++ .../connector/entity/FishingHookEntity.java | 158 ++++++++++++++++-- .../entity/ItemedFireballEntity.java | 41 ++++- .../connector/entity/ThrowableEntity.java | 79 +++++++-- .../connector/entity/WitherSkullEntity.java | 2 + .../connector/entity/type/EntityType.java | 4 +- .../collision/CollisionManager.java | 24 +-- .../spawn/JavaSpawnEntityTranslator.java | 15 +- .../world/block/BlockStateValues.java | 65 ++++--- .../world/block/BlockTranslator.java | 2 +- connector/src/main/resources/mappings | 2 +- 11 files changed, 332 insertions(+), 78 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java b/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java index e9a4a1f98..70dbdf959 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/AbstractArrowEntity.java @@ -35,6 +35,8 @@ public class AbstractArrowEntity extends Entity { public AbstractArrowEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + + setMotion(motion); } @Override @@ -47,4 +49,20 @@ public class AbstractArrowEntity extends Entity { super.updateBedrockMetadata(entityMetadata, session); } + + @Override + public void setRotation(Vector3f rotation) { + // Ignore the rotation sent by the Java server since the + // Java client calculates the rotation from the motion + } + + @Override + public void setMotion(Vector3f motion) { + super.setMotion(motion); + + double horizontalSpeed = Math.sqrt(motion.getX() * motion.getX() + motion.getZ() * motion.getZ()); + float yaw = (float) Math.toDegrees(Math.atan2(motion.getX(), motion.getZ())); + float pitch = (float) Math.toDegrees(Math.atan2(motion.getY(), horizontalSpeed)); + rotation = Vector3f.from(yaw, pitch, yaw); + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java index 9f9b7bc38..06e56ad03 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java @@ -26,43 +26,167 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.object.ProjectileData; import com.nukkitx.math.vector.Vector3f; +import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.entity.EntityData; -import org.geysermc.connector.GeyserConnector; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.packet.PlaySoundPacket; +import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.collision.BoundingBox; +import org.geysermc.connector.network.translators.collision.CollisionManager; +import org.geysermc.connector.network.translators.collision.CollisionTranslator; +import org.geysermc.connector.network.translators.collision.translators.BlockCollision; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; -public class FishingHookEntity extends Entity { - public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, ProjectileData data) { +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class FishingHookEntity extends ThrowableEntity { + + private boolean hooked = false; + + private final BoundingBox boundingBox; + + private boolean inWater = false; + + public FishingHookEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, PlayerEntity owner) { super(entityId, geyserId, entityType, position, motion, rotation); - for (GeyserSession session : GeyserConnector.getInstance().getPlayers()) { - Entity entity = session.getEntityCache().getEntityByJavaId(data.getOwnerId()); - if (entity == null && session.getPlayerEntity().getEntityId() == data.getOwnerId()) { - entity = session.getPlayerEntity(); - } + this.boundingBox = new BoundingBox(0.125, 0.125, 0.125, 0.25, 0.25, 0.25); - if (entity != null) { - this.metadata.put(EntityData.OWNER_EID, entity.getGeyserId()); - return; - } - } + // In Java, the splash sound depends on the entity's velocity, but in Bedrock the volume doesn't change. + // This splash can be confused with the sound from catching a fish. This silences the splash from Bedrock, + // so that it can be handled by moveAbsoluteImmediate. + this.metadata.putFloat(EntityData.BOUNDING_BOX_HEIGHT, 128); + + this.metadata.put(EntityData.OWNER_EID, owner.getGeyserId()); } @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { - if (entityMetadata.getId() == 7) { - Entity entity = session.getEntityCache().getEntityByJavaId((Integer) entityMetadata.getValue() - 1); - if (entity == null && session.getPlayerEntity().getEntityId() == (Integer) entityMetadata.getValue() - 1) { + if (entityMetadata.getId() == 7) { // Hooked entity + int hookedEntityId = (int) entityMetadata.getValue() - 1; + Entity entity = session.getEntityCache().getEntityByJavaId(hookedEntityId); + if (entity == null && session.getPlayerEntity().getEntityId() == hookedEntityId) { entity = session.getPlayerEntity(); } if (entity != null) { metadata.put(EntityData.TARGET_EID, entity.getGeyserId()); + hooked = true; + } else { + hooked = false; } } super.updateBedrockMetadata(entityMetadata, session); } + + @Override + protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + boundingBox.setMiddleX(position.getX()); + boundingBox.setMiddleY(position.getY() + boundingBox.getSizeY() / 2); + boundingBox.setMiddleZ(position.getZ()); + + CollisionManager collisionManager = session.getCollisionManager(); + List collidableBlocks = collisionManager.getCollidableBlocks(boundingBox); + boolean touchingWater = false; + boolean collided = false; + for (Vector3i blockPos : collidableBlocks) { + if (0 <= blockPos.getY() && blockPos.getY() <= 255) { + int blockID = session.getConnector().getWorldManager().getBlockAt(session, blockPos); + BlockCollision blockCollision = CollisionTranslator.getCollision(blockID, blockPos.getX(), blockPos.getY(), blockPos.getZ()); + if (blockCollision != null && blockCollision.checkIntersection(boundingBox)) { + // TODO Push bounding box out of collision to improve movement + collided = true; + } + + int waterLevel = BlockStateValues.getWaterLevel(blockID); + if (BlockTranslator.isWaterlogged(blockID)) { + waterLevel = 0; + } + if (waterLevel >= 0) { + double waterMaxY = blockPos.getY() + 1 - (waterLevel + 1) / 9.0; + // Falling water is a full block + if (waterLevel >= 8) { + waterMaxY = blockPos.getY() + 1; + } + if (position.getY() <= waterMaxY) { + touchingWater = true; + } + } + } + } + + if (!inWater && touchingWater) { + sendSplashSound(session); + } + inWater = touchingWater; + + if (!collided) { + super.moveAbsoluteImmediate(session, position, rotation, isOnGround, teleported); + } else { + super.moveAbsoluteImmediate(session, this.position, rotation, true, true); + } + } + + private void sendSplashSound(GeyserSession session) { + if (!metadata.getFlags().getFlag(EntityFlag.SILENT)) { + float volume = (float) (0.2f * Math.sqrt(0.2 * (motion.getX() * motion.getX() + motion.getZ() * motion.getZ()) + motion.getY() * motion.getY())); + if (volume > 1) { + volume = 1; + } + PlaySoundPacket playSoundPacket = new PlaySoundPacket(); + playSoundPacket.setSound("random.splash"); + playSoundPacket.setPosition(position); + playSoundPacket.setVolume(volume); + playSoundPacket.setPitch(1f + ThreadLocalRandom.current().nextFloat() * 0.3f); + session.sendUpstreamPacket(playSoundPacket); + } + } + + @Override + public void tick(GeyserSession session) { + if (hooked || !isInAir(session) && !isInWater(session) || isOnGround()) { + motion = Vector3f.ZERO; + return; + } + float gravity = getGravity(session); + motion = motion.down(gravity); + + moveAbsoluteImmediate(session, position.add(motion), rotation, onGround, false); + + float drag = getDrag(session); + motion = motion.mul(drag); + } + + @Override + protected float getGravity(GeyserSession session) { + if (!isInWater(session) && !onGround) { + return 0.03f; + } + return 0; + } + + /** + * @param session the session of the Bedrock client. + * @return true if this entity is currently in air. + */ + protected boolean isInAir(GeyserSession session) { + if (session.getConnector().getConfig().isCacheChunks()) { + if (0 <= position.getFloorY() && position.getFloorY() <= 255) { + int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); + return block == BlockTranslator.JAVA_AIR_ID; + } + } + return false; + } + + @Override + protected float getDrag(GeyserSession session) { + return 0.92f; + } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java index 2b411109a..58a3b6f6c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemedFireballEntity.java @@ -32,19 +32,44 @@ import org.geysermc.connector.network.session.GeyserSession; public class ItemedFireballEntity extends ThrowableEntity { private final Vector3f acceleration; + /** + * The number of ticks to advance movement before sending to Bedrock + */ + protected int futureTicks = 3; + public ItemedFireballEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, Vector3f.ZERO, rotation); - acceleration = motion; + + float magnitude = motion.length(); + if (magnitude != 0) { + acceleration = motion.div(magnitude).mul(0.1f); + } else { + acceleration = Vector3f.ZERO; + } + } + + private Vector3f tickMovement(GeyserSession session, Vector3f position) { + position = position.add(motion); + float drag = getDrag(session); + motion = motion.add(acceleration).mul(drag); + return position; + } + + @Override + protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { + // Advance the position by a few ticks before sending it to Bedrock + Vector3f lastMotion = motion; + Vector3f newPosition = position; + for (int i = 0; i < futureTicks; i++) { + newPosition = tickMovement(session, newPosition); + } + super.moveAbsoluteImmediate(session, newPosition, rotation, isOnGround, teleported); + this.position = position; + this.motion = lastMotion; } @Override public void tick(GeyserSession session) { - position = position.add(motion); - // TODO: While this reduces latency in position updating (needed for better fireball reflecting), - // TODO: movement is incredibly stiff. - // TODO: Only use this laggy movement for fireballs that be reflected - moveAbsoluteImmediate(session, position, rotation, false, true); - float drag = getDrag(session); - motion = motion.add(acceleration).mul(drag); + moveAbsoluteImmediate(session, tickMovement(session, position), rotation, false, false); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java index 4e0c25ab5..1088b2a0b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ThrowableEntity.java @@ -29,20 +29,21 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; +import com.nukkitx.protocol.bedrock.packet.MoveEntityDeltaPacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; +import org.geysermc.connector.network.translators.world.block.BlockStateValues; /** * Used as a class for any object-like entity that moves as a projectile */ public class ThrowableEntity extends Entity implements Tickable { - private Vector3f lastPosition; + protected Vector3f lastJavaPosition; public ThrowableEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); - this.lastPosition = position; + this.lastJavaPosition = position; } /** @@ -52,22 +53,65 @@ public class ThrowableEntity extends Entity implements Tickable { */ @Override public void tick(GeyserSession session) { - super.moveRelative(session, motion.getX(), motion.getY(), motion.getZ(), rotation, onGround); + moveAbsoluteImmediate(session, position.add(motion), rotation, onGround, false); float drag = getDrag(session); - float gravity = getGravity(); + float gravity = getGravity(session); motion = motion.mul(drag).down(gravity); } protected void moveAbsoluteImmediate(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - super.moveAbsolute(session, position, rotation, isOnGround, teleported); + MoveEntityDeltaPacket moveEntityDeltaPacket = new MoveEntityDeltaPacket(); + moveEntityDeltaPacket.setRuntimeEntityId(geyserId); + + if (isOnGround) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.ON_GROUND); + } + setOnGround(isOnGround); + + if (teleported) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.TELEPORTING); + } + + if (this.position.getX() != position.getX()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_X); + moveEntityDeltaPacket.setX(position.getX()); + } + if (this.position.getY() != position.getY()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Y); + moveEntityDeltaPacket.setY(position.getY()); + } + if (this.position.getZ() != position.getZ()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_Z); + moveEntityDeltaPacket.setZ(position.getZ()); + } + setPosition(position); + + if (this.rotation.getX() != rotation.getX()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_YAW); + moveEntityDeltaPacket.setYaw(rotation.getX()); + } + if (this.rotation.getY() != rotation.getY()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_PITCH); + moveEntityDeltaPacket.setPitch(rotation.getY()); + } + if (this.rotation.getZ() != rotation.getZ()) { + moveEntityDeltaPacket.getFlags().add(MoveEntityDeltaPacket.Flag.HAS_HEAD_YAW); + moveEntityDeltaPacket.setHeadYaw(rotation.getZ()); + } + setRotation(rotation); + + if (!moveEntityDeltaPacket.getFlags().isEmpty()) { + session.sendUpstreamPacket(moveEntityDeltaPacket); + } } /** * Get the gravity of this entity type. Used for applying gravity while the entity is in motion. * + * @param session the session of the Bedrock client. * @return the amount of gravity to apply to this entity while in motion. */ - protected float getGravity() { + protected float getGravity(GeyserSession session) { if (metadata.getFlags().getFlag(EntityFlag.HAS_GRAVITY)) { switch (entityType) { case THROWN_POTION: @@ -76,11 +120,14 @@ public class ThrowableEntity extends Entity implements Tickable { case THROWN_EXP_BOTTLE: return 0.07f; case FIREBALL: + case SHULKER_BULLET: return 0; case SNOWBALL: case THROWN_EGG: case THROWN_ENDERPEARL: return 0.03f; + case LLAMA_SPIT: + return 0.06f; } } return 0; @@ -101,11 +148,14 @@ public class ThrowableEntity extends Entity implements Tickable { case SNOWBALL: case THROWN_EGG: case THROWN_ENDERPEARL: + case LLAMA_SPIT: return 0.99f; case FIREBALL: case SMALL_FIREBALL: case DRAGON_FIREBALL: return 0.95f; + case SHULKER_BULLET: + return 1; } } return 1; @@ -117,8 +167,10 @@ public class ThrowableEntity extends Entity implements Tickable { */ protected boolean isInWater(GeyserSession session) { if (session.getConnector().getConfig().isCacheChunks()) { - int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); - return block == BlockTranslator.BEDROCK_WATER_ID; + if (0 <= position.getFloorY() && position.getFloorY() <= 255) { + int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); + return BlockStateValues.getWaterLevel(block) != -1; + } } return false; } @@ -136,14 +188,13 @@ public class ThrowableEntity extends Entity implements Tickable { @Override public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { - position = lastPosition; - super.moveRelative(session, relX, relY, relZ, rotation, isOnGround); - lastPosition = position; + moveAbsoluteImmediate(session, lastJavaPosition.add(relX, relY, relZ), rotation, isOnGround, false); + lastJavaPosition = position; } @Override public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - super.moveAbsolute(session, position, rotation, isOnGround, teleported); - lastPosition = position; + moveAbsoluteImmediate(session, position, rotation, isOnGround, teleported); + lastJavaPosition = position; } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java b/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java index ba5b9eb55..3548e0dfe 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/WitherSkullEntity.java @@ -35,6 +35,8 @@ public class WitherSkullEntity extends ItemedFireballEntity { public WitherSkullEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); + + this.futureTicks = 1; } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java index c45a38ad0..f38e56fd8 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java +++ b/connector/src/main/java/org/geysermc/connector/entity/type/EntityType.java @@ -118,7 +118,7 @@ public enum EntityType { TRIDENT(TridentEntity.class, 73, 0f, 0f, 0f, 0f, "minecraft:thrown_trident"), TURTLE(TurtleEntity.class, 74, 0.4f, 1.2f), CAT(CatEntity.class, 75, 0.35f, 0.3f), - SHULKER_BULLET(Entity.class, 76, 0.3125f), + SHULKER_BULLET(ThrowableEntity.class, 76, 0.3125f), FISHING_BOBBER(FishingHookEntity.class, 77, 0f, 0f, 0f, 0f, "minecraft:fishing_hook"), CHALKBOARD(Entity.class, 78, 0f), DRAGON_FIREBALL(ItemedFireballEntity.class, 79, 1.0f), @@ -145,7 +145,7 @@ public enum EntityType { MINECART_SPAWNER(SpawnerMinecartEntity.class, 98, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:minecart"), MINECART_COMMAND_BLOCK(CommandBlockMinecartEntity.class, 100, 0.7f, 0.98f, 0.98f, 0.35f, "minecraft:command_block_minecart"), LINGERING_POTION(ThrowableEntity.class, 101, 0f), - LLAMA_SPIT(Entity.class, 102, 0.25f), + LLAMA_SPIT(ThrowableEntity.class, 102, 0.25f), EVOKER_FANGS(Entity.class, 103, 0.8f, 0.5f, 0.5f, 0f, "minecraft:evocation_fang"), EVOKER(SpellcasterIllagerEntity.class, 104, 1.95f, 0.6f, 0.6f, 0f, "minecraft:evocation_illager"), VEX(VexEntity.class, 105, 0.8f, 0.4f), diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java index 203e4406f..77392a99a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/collision/CollisionManager.java @@ -177,24 +177,24 @@ public class CollisionManager { session.sendUpstreamPacket(movePlayerPacket); } - public List getPlayerCollidableBlocks() { + public List getCollidableBlocks(BoundingBox box) { List blocks = new ArrayList<>(); - Vector3d position = Vector3d.from(playerBoundingBox.getMiddleX(), - playerBoundingBox.getMiddleY() - (playerBoundingBox.getSizeY() / 2), - playerBoundingBox.getMiddleZ()); + Vector3d position = Vector3d.from(box.getMiddleX(), + box.getMiddleY() - (box.getSizeY() / 2), + box.getMiddleZ()); - // Loop through all blocks that could collide with the player - int minCollisionX = (int) Math.floor(position.getX() - ((playerBoundingBox.getSizeX() / 2) + COLLISION_TOLERANCE)); - int maxCollisionX = (int) Math.floor(position.getX() + (playerBoundingBox.getSizeX() / 2) + COLLISION_TOLERANCE); + // Loop through all blocks that could collide + int minCollisionX = (int) Math.floor(position.getX() - ((box.getSizeX() / 2) + COLLISION_TOLERANCE)); + int maxCollisionX = (int) Math.floor(position.getX() + (box.getSizeX() / 2) + COLLISION_TOLERANCE); // Y extends 0.5 blocks down because of fence hitboxes int minCollisionY = (int) Math.floor(position.getY() - 0.5); - int maxCollisionY = (int) Math.floor(position.getY() + playerBoundingBox.getSizeY()); + int maxCollisionY = (int) Math.floor(position.getY() + box.getSizeY()); - int minCollisionZ = (int) Math.floor(position.getZ() - ((playerBoundingBox.getSizeZ() / 2) + COLLISION_TOLERANCE)); - int maxCollisionZ = (int) Math.floor(position.getZ() + (playerBoundingBox.getSizeZ() / 2) + COLLISION_TOLERANCE); + int minCollisionZ = (int) Math.floor(position.getZ() - ((box.getSizeZ() / 2) + COLLISION_TOLERANCE)); + int maxCollisionZ = (int) Math.floor(position.getZ() + (box.getSizeZ() / 2) + COLLISION_TOLERANCE); for (int y = minCollisionY; y < maxCollisionY + 1; y++) { for (int x = minCollisionX; x < maxCollisionX + 1; x++) { @@ -207,6 +207,10 @@ public class CollisionManager { return blocks; } + public List getPlayerCollidableBlocks() { + return getCollidableBlocks(playerBoundingBox); + } + /** * Returns false if the movement is invalid, and in this case it shouldn't be sent to the server and should be * cancelled diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java index efbdfc5b2..b6a41fa42 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnEntityTranslator.java @@ -32,6 +32,7 @@ import com.github.steveice10.mc.protocol.data.game.entity.type.EntityType; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.spawn.ServerSpawnEntityPacket; import com.nukkitx.math.vector.Vector3f; import org.geysermc.connector.entity.*; +import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -69,8 +70,18 @@ public class JavaSpawnEntityTranslator extends PacketTranslator entry, int javaBlockState) { - JsonNode bannerColor = entry.getValue().get("banner_color"); + public static void storeBlockStateValues(String javaId, int javaBlockState, JsonNode blockData) { + JsonNode bannerColor = blockData.get("banner_color"); if (bannerColor != null) { BANNER_COLORS.put(javaBlockState, (byte) bannerColor.intValue()); return; // There will never be a banner color and a skull variant } - JsonNode bedColor = entry.getValue().get("bed_color"); + JsonNode bedColor = blockData.get("bed_color"); if (bedColor != null) { BED_COLORS.put(javaBlockState, (byte) bedColor.intValue()); return; } - if (entry.getKey().contains("command_block")) { - COMMAND_BLOCK_VALUES.put(javaBlockState, entry.getKey().contains("conditional=true") ? (byte) 1 : (byte) 0); + if (javaId.contains("command_block")) { + COMMAND_BLOCK_VALUES.put(javaBlockState, javaId.contains("conditional=true") ? (byte) 1 : (byte) 0); return; } - if (entry.getValue().get("double_chest_position") != null) { - boolean isX = (entry.getValue().get("x") != null); - boolean isDirectionPositive = ((entry.getValue().get("x") != null && entry.getValue().get("x").asBoolean()) || - (entry.getValue().get("z") != null && entry.getValue().get("z").asBoolean())); - boolean isLeft = (entry.getValue().get("double_chest_position").asText().contains("left")); + if (blockData.get("double_chest_position") != null) { + boolean isX = (blockData.get("x") != null); + boolean isDirectionPositive = ((blockData.get("x") != null && blockData.get("x").asBoolean()) || + (blockData.get("z") != null && blockData.get("z").asBoolean())); + boolean isLeft = (blockData.get("double_chest_position").asText().contains("left")); DOUBLE_CHEST_VALUES.put(javaBlockState, new DoubleChestValue(isX, isDirectionPositive, isLeft)); return; } - if (entry.getKey().contains("potted_") || entry.getKey().contains("flower_pot")) { - FLOWER_POT_VALUES.put(javaBlockState, entry.getKey().replace("potted_", "")); + if (javaId.contains("potted_") || javaId.contains("flower_pot")) { + FLOWER_POT_VALUES.put(javaBlockState, javaId.replace("potted_", "")); return; } - JsonNode notePitch = entry.getValue().get("note_pitch"); + JsonNode notePitch = blockData.get("note_pitch"); if (notePitch != null) { - NOTEBLOCK_PITCHES.put(javaBlockState, entry.getValue().get("note_pitch").intValue()); + NOTEBLOCK_PITCHES.put(javaBlockState, blockData.get("note_pitch").intValue()); return; } - if (entry.getKey().contains("piston")) { + if (javaId.contains("piston")) { // True if extended, false if not - PISTON_VALUES.put(javaBlockState, entry.getKey().contains("extended=true")); - IS_STICKY_PISTON.put(javaBlockState, entry.getKey().contains("sticky")); + PISTON_VALUES.put(javaBlockState, javaId.contains("extended=true")); + IS_STICKY_PISTON.put(javaBlockState, javaId.contains("sticky")); return; } - JsonNode skullVariation = entry.getValue().get("variation"); + JsonNode skullVariation = blockData.get("variation"); if (skullVariation != null) { SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue()); } - JsonNode skullRotation = entry.getValue().get("skull_rotation"); + JsonNode skullRotation = blockData.get("skull_rotation"); if (skullRotation != null) { SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue()); } - if (entry.getKey().contains("wall_skull") || entry.getKey().contains("wall_head")) { - String direction = entry.getKey().substring(entry.getKey().lastIndexOf("facing=") + 7); + if (javaId.contains("wall_skull") || javaId.contains("wall_head")) { + String direction = javaId.substring(javaId.lastIndexOf("facing=") + 7); int rotation = 0; switch (direction.substring(0, direction.length() - 1)) { case "north": @@ -131,10 +133,16 @@ public class BlockStateValues { SKULL_WALL_DIRECTIONS.put(javaBlockState, rotation); } - JsonNode shulkerDirection = entry.getValue().get("shulker_direction"); + JsonNode shulkerDirection = blockData.get("shulker_direction"); if (shulkerDirection != null) { BlockStateValues.SHULKERBOX_DIRECTIONS.put(javaBlockState, (byte) shulkerDirection.intValue()); } + + if (javaId.startsWith("minecraft:water")) { + String strLevel = javaId.substring(javaId.lastIndexOf("level=") + 6, javaId.length() - 1); + int level = Integer.parseInt(strLevel); + WATER_LEVEL.put(javaBlockState, level); + } } /** @@ -263,4 +271,15 @@ public class BlockStateValues { public static byte getShulkerBoxDirection(int state) { return SHULKERBOX_DIRECTIONS.getOrDefault(state, (byte) -1); } + + /** + * Get the level of water from the block state. + * This is used in FishingHookEntity to create splash sounds when the hook hits the water. + * + * @param state BlockState of the block + * @return The water level or -1 if the block isn't water + */ + public static int getWaterLevel(int state) { + return WATER_LEVEL.getOrDefault(state, -1); + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index 06d724728..1d7c86a53 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -185,7 +185,7 @@ public class BlockTranslator { JAVA_ID_BLOCK_MAP.put(javaId, javaRuntimeId); - BlockStateValues.storeBlockStateValues(entry, javaRuntimeId); + BlockStateValues.storeBlockStateValues(entry.getKey(), javaRuntimeId, entry.getValue()); String cleanJavaIdentifier = entry.getKey().split("\\[")[0]; diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 2e52b01cc..bf0610450 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 2e52b01cc541c8925346f93be8940087d9af1661 +Subproject commit bf0610450ce94507a18286e94af2965550ff9eaa From f849a5b9f99f38073d775cf85957c93eb094bb0e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 20 Feb 2021 23:52:49 -0500 Subject: [PATCH 090/161] A bunch of fixes --- .../connector/inventory/Inventory.java | 15 ++++++- .../network/UpstreamPacketHandler.java | 1 + .../network/session/GeyserSession.java | 2 +- .../inventory/InventoryTranslator.java | 41 +++++++++---------- .../holder/BlockInventoryHolder.java | 19 +++++++-- .../chest/SingleChestInventoryTranslator.java | 13 +++++- .../window/JavaCloseWindowTranslator.java | 5 ++- .../java/window/JavaOpenWindowTranslator.java | 6 +-- 8 files changed, 69 insertions(+), 33 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java index 7048ce808..535a9077a 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -36,7 +36,7 @@ import java.util.Arrays; public class Inventory { @Getter - protected int id; + protected final int id; @Getter protected final int size; @@ -96,4 +96,17 @@ public class Inventory { public short getNextTransactionId() { return ++transactionId; } + + @Override + public String toString() { + return "Inventory{" + + "id=" + id + + ", size=" + size + + ", title='" + title + '\'' + + ", items=" + Arrays.toString(items) + + ", holderPosition=" + holderPosition + + ", holderId=" + holderId + + ", transactionId=" + transactionId + + '}'; + } } diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index 7ebfaeda5..1245fe558 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -47,6 +47,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { } private boolean translateAndDefault(BedrockPacket packet) { + System.out.println(packet.toString()); return PacketTranslatorRegistry.BEDROCK_TRANSLATOR.translate(packet.getClass(), packet, session); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 080e8bf02..41269ebf1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -144,7 +144,7 @@ public class GeyserSession implements CommandSender { * Use {@link #getNextItemNetId()} instead for consistency */ @Getter(AccessLevel.NONE) - private final AtomicInteger itemNetId = new AtomicInteger(1); + private final AtomicInteger itemNetId = new AtomicInteger(2); @Getter(AccessLevel.NONE) private final Object inventoryLock = new Object(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 7a9b890b3..d924c1b88 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -85,6 +85,8 @@ public abstract class InventoryTranslator { put(WindowType.CARTOGRAPHY, new CartographyInventoryTranslator()); put(WindowType.CRAFTING, new CraftingInventoryTranslator()); put(WindowType.ENCHANTMENT, new EnchantingInventoryTranslator()); + put(WindowType.HOPPER, new HopperInventoryTranslator()); + put(WindowType.GENERIC_3X3, new Generic3X3InventoryTranslator()); put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); put(WindowType.LOOM, new LoomInventoryTranslator()); put(WindowType.MERCHANT, new MerchantInventoryTranslator()); @@ -92,10 +94,6 @@ public abstract class InventoryTranslator { put(WindowType.SMITHING, new SmithingInventoryTranslator()); put(WindowType.STONECUTTER, new StonecutterInventoryTranslator()); - /* Generics */ - put(WindowType.GENERIC_3X3, new Generic3X3InventoryTranslator()); - put(WindowType.HOPPER, new HopperInventoryTranslator()); - /* Lectern */ put(WindowType.LECTERN, new LecternInventoryTranslator()); } @@ -195,9 +193,11 @@ public abstract class InventoryTranslator { transferAction.getSource().getSlot() >= 28 && transferAction.getSource().getSlot() <= 31) { return rejectRequest(request, false); } - session.getConnector().getLogger().error("DEBUG: About to reject request made by " + session.getName()); + session.getConnector().getLogger().error("DEBUG: About to reject TAKE/PLACE request made by " + session.getName()); session.getConnector().getLogger().error("Source: " + transferAction.getSource().toString() + " Result: " + checkNetId(session, inventory, transferAction.getSource())); session.getConnector().getLogger().error("Destination: " + transferAction.getDestination().toString() + " Result: " + checkNetId(session, inventory, transferAction.getDestination())); + session.getConnector().getLogger().error("Geyser's record of source slot: " + inventory.getItem(bedrockSlotToJava(transferAction.getSource()))); + session.getConnector().getLogger().error("Geyser's record of destination slot: " + inventory.getItem(bedrockSlotToJava(transferAction.getDestination()))); return rejectRequest(request); } @@ -278,8 +278,14 @@ public abstract class InventoryTranslator { } case SWAP: { //TODO SwapStackRequestActionData swapAction = (SwapStackRequestActionData) action; - if (!(checkNetId(session, inventory, swapAction.getSource()) && checkNetId(session, inventory, swapAction.getDestination()))) + if (!(checkNetId(session, inventory, swapAction.getSource()) && checkNetId(session, inventory, swapAction.getDestination()))) { + session.getConnector().getLogger().error("DEBUG: About to reject SWAP request made by " + session.getName()); + session.getConnector().getLogger().error("Source: " + swapAction.getSource().toString() + " Result: " + checkNetId(session, inventory, swapAction.getSource())); + session.getConnector().getLogger().error("Destination: " + swapAction.getDestination().toString() + " Result: " + checkNetId(session, inventory, swapAction.getDestination())); + session.getConnector().getLogger().error("Geyser's record of source slot: " + inventory.getItem(bedrockSlotToJava(swapAction.getSource()))); + session.getConnector().getLogger().error("Geyser's record of destination slot: " + inventory.getItem(bedrockSlotToJava(swapAction.getDestination()))); return rejectRequest(request); + } if (isCursor(swapAction.getSource()) && isCursor(swapAction.getDestination())) { //??? return rejectRequest(request); @@ -361,14 +367,9 @@ public abstract class InventoryTranslator { } break; } - case CRAFT_NON_IMPLEMENTED_DEPRECATED: { - break; - } - case CRAFT_RESULTS_DEPRECATED: { - break; - } - case CRAFT_RECIPE_OPTIONAL: { - // Anvils and cartography tables will handle this + case CRAFT_NON_IMPLEMENTED_DEPRECATED: + case CRAFT_RESULTS_DEPRECATED: + case CRAFT_RECIPE_OPTIONAL: { // Anvils and cartography tables will handle this break; } default: @@ -381,9 +382,8 @@ public abstract class InventoryTranslator { } public ItemStackResponsePacket.Response translateCraftingRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { - int recipeId = 0; int resultSize = 0; - int timesCrafted = 0; + int timesCrafted; CraftState craftState = CraftState.START; int leftover = 0; @@ -396,7 +396,6 @@ public abstract class InventoryTranslator { return rejectRequest(request); } craftState = CraftState.RECIPE_ID; - recipeId = craftAction.getRecipeNetworkId(); break; } case CRAFT_RESULTS_DEPRECATED: { @@ -417,7 +416,6 @@ public abstract class InventoryTranslator { break; } case CONSUME: { - ConsumeStackRequestActionData consumeAction = (ConsumeStackRequestActionData) action; if (craftState != CraftState.DEPRECATED && craftState != CraftState.INGREDIENTS) { return rejectRequest(request); } @@ -507,9 +505,7 @@ public abstract class InventoryTranslator { Int2IntMap consumedSlots = new Int2IntOpenHashMap(); int prioritySlot = -1; - int secondarySlot = -1; - int tempSlot = -1; - boolean intoCursor = false; + int tempSlot; int resultSize; int timesCrafted = 0; @@ -626,7 +622,6 @@ public abstract class InventoryTranslator { int javaSlot = bedrockSlotToJava(transferAction.getDestination()); if (isCursor(transferAction.getDestination())) { //TODO - intoCursor = true; if (timesCrafted > 1) { tempSlot = findTempSlot(inventory, GeyserItemStack.from(output), true); if (tempSlot == -1) { @@ -735,6 +730,8 @@ public abstract class InventoryTranslator { public boolean checkNetId(GeyserSession session, Inventory inventory, StackRequestSlotInfoData slotInfoData) { int netId = slotInfoData.getStackNetworkId(); + // "In my testing, sometimes the client thinks the netId of an item in the crafting grid is 1, even though we never said it was. + // I think it only happens when we manually set the grid but that was my quick fix" if (netId < 0 || netId == 1) return true; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index 24be49144..3186a85e9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -31,6 +31,7 @@ import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket; import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.connector.inventory.Container; @@ -76,11 +77,11 @@ public class BlockInventoryHolder extends InventoryHolder { if (session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition())) { // Then, check to see if the interacted block is valid for this inventory by ensuring the block state identifier is valid int javaBlockId = session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition()); - String javaBlockString = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(javaBlockId, "minecraft:air").split("\\[")[0]; - if (this.validBlocks.contains(javaBlockString)) { + String[] javaBlockString = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(javaBlockId, "minecraft:air").split("\\["); + if (isValidBlock(javaBlockString)) { // We can safely use this block inventory.setHolderPosition(session.getLastInteractionBlockPosition()); - ((Container) inventory).setUsingRealBlock(true, javaBlockString); + ((Container) inventory).setUsingRealBlock(true, javaBlockString[0]); setCustomName(session, session.getLastInteractionBlockPosition(), inventory); return; } @@ -100,6 +101,13 @@ public class BlockInventoryHolder extends InventoryHolder { setCustomName(session, position, inventory); } + /** + * @return true if this Java block ID can be used for player inventory. + */ + protected boolean isValidBlock(String[] javaBlockString) { + return this.validBlocks.contains(javaBlockString[0]); + } + protected void setCustomName(GeyserSession session, Vector3i position, Inventory inventory) { NbtMap tag = NbtMap.builder() .putInt("x", position.getX()) @@ -126,6 +134,11 @@ public class BlockInventoryHolder extends InventoryHolder { public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { if (((Container) inventory).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.getId()); + packet.setUnknownBool0(true); //TODO needs to be changed in Protocol to "server-side" or something + session.sendUpstreamPacket(packet); return; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java index 7b9b12c4f..42b23d5b4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java @@ -37,7 +37,18 @@ public class SingleChestInventoryTranslator extends ChestInventoryTranslator { public SingleChestInventoryTranslator(int size) { super(size, 27); this.holder = new BlockInventoryHolder("minecraft:chest[facing=north,type=single,waterlogged=false]", ContainerType.CONTAINER, - "minecraft:ender_chest", "minecraft:trapped_chest"); + "minecraft:ender_chest", "minecraft:trapped_chest") { + @Override + protected boolean isValidBlock(String[] javaBlockString) { + if (javaBlockString[0].equals("minecraft:ender_chest")) { + // Can't have double ender chests + return true; + } + + // Add provision to ensure this isn't a double chest + return super.isValidBlock(javaBlockString) && (javaBlockString.length > 1 && javaBlockString[1].contains("type=single")); + } + }; } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java index b78dc32f2..6d3ae0ab1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java @@ -36,6 +36,9 @@ public class JavaCloseWindowTranslator extends PacketTranslator InventoryUtils.closeInventory(session, packet.getWindowId())); + session.addInventoryTask(() -> { + session.getConnector().getLogger().info("Closing window ID " + packet.getWindowId()); + InventoryUtils.closeInventory(session, packet.getWindowId()); + }); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 99af2e73e..fa83de7c2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -45,6 +45,7 @@ public class JavaOpenWindowTranslator extends PacketTranslator Date: Sat, 20 Feb 2021 23:54:15 -0500 Subject: [PATCH 091/161] oops --- .../geysermc/connector/network/UpstreamPacketHandler.java | 1 - .../translators/java/window/JavaCloseWindowTranslator.java | 5 +---- .../translators/java/window/JavaOpenWindowTranslator.java | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index 1245fe558..7ebfaeda5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -47,7 +47,6 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { } private boolean translateAndDefault(BedrockPacket packet) { - System.out.println(packet.toString()); return PacketTranslatorRegistry.BEDROCK_TRANSLATOR.translate(packet.getClass(), packet, session); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java index 6d3ae0ab1..b78dc32f2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java @@ -36,9 +36,6 @@ public class JavaCloseWindowTranslator extends PacketTranslator { - session.getConnector().getLogger().info("Closing window ID " + packet.getWindowId()); - InventoryUtils.closeInventory(session, packet.getWindowId()); - }); + session.addInventoryTask(() -> InventoryUtils.closeInventory(session, packet.getWindowId())); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index fa83de7c2..2d1f77317 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -45,7 +45,6 @@ public class JavaOpenWindowTranslator extends PacketTranslator Date: Sun, 21 Feb 2021 18:17:31 -0500 Subject: [PATCH 092/161] Small, inconsequential changes --- .../network/translators/world/GeyserWorldManager.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java index 8929b7bfa..6d2d8720d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java @@ -37,6 +37,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.ChunkCache; import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.GameRule; public class GeyserWorldManager extends WorldManager { @@ -49,7 +50,7 @@ public class GeyserWorldManager extends WorldManager { if (chunkCache != null) { // Chunk cache can be null if the session is closed asynchronously return chunkCache.getBlockAt(x, y, z); } - return 0; + return BlockTranslator.JAVA_AIR_ID; } @Override @@ -105,7 +106,7 @@ public class GeyserWorldManager extends WorldManager { .putString("text", "") .build()) .build()); - lecternTag.putInt("page", -1); // I'm surprisingly glad this exists - it forces Bedrock to stop reading immediately + lecternTag.putInt("page", -1); // I'm surprisingly glad this exists - it forces Bedrock to stop reading immediately. Usually. return lecternTag.build(); } From f480fbfdf68aa09dbb63b0a08a9857143b218030 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 21 Feb 2021 18:57:09 -0500 Subject: [PATCH 093/161] More changes to allow Geyser to cooperate better with bad internet (#1951) --- .../geysermc/connector/utils/LocaleUtils.java | 16 +++++++++++++--- .../org/geysermc/connector/utils/WebUtils.java | 3 +-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java index dea05cca0..db5457244 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/LocaleUtils.java @@ -147,6 +147,12 @@ public class LocaleUtils { } } } catch (IOException ignored) { } + + if (clientJarInfo == null) { + // Likely failed to download + GeyserConnector.getInstance().getLogger().debug("Skipping en_US hash check as client jar is null."); + return; + } targetHash = clientJarInfo.getSha1(); } else { curHash = byteArrayToHexString(FileUtils.calculateSHA1(localeFile)); @@ -168,9 +174,13 @@ public class LocaleUtils { return; } - // Get the hash and download the locale - String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); - WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString()); + try { + // Get the hash and download the locale + String hash = ASSET_MAP.get("minecraft/lang/" + locale + ".json").getHash(); + WebUtils.downloadFile("https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash, localeFile.toString()); + } catch (Exception e) { + GeyserConnector.getInstance().getLogger().error("Unable to download locale file hash", e); + } } /** diff --git a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java index 2d69c9adf..ba008da41 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/WebUtils.java @@ -88,8 +88,7 @@ public class WebUtils { } public static String post(String reqURL, String postContent) throws IOException { - URL url = null; - url = new URL(reqURL); + URL url = new URL(reqURL); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("POST"); con.setRequestProperty("Content-Type", "text/plain"); From 58e2c7d709242708f1b174250598356b8b70cdc3 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 23 Feb 2021 21:26:41 +0100 Subject: [PATCH 094/161] Use the new form issue templates (#1973) --- .github/ISSUE_TEMPLATE/bug_report.md | 57 ------------------- .github/ISSUE_TEMPLATE/bug_report.yml | 64 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 10 +++- .github/ISSUE_TEMPLATE/feature_request.md | 14 ----- .github/ISSUE_TEMPLATE/feature_request.yml | 21 +++++++ 5 files changed, 93 insertions(+), 73 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 17e88f268..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - - - - - - - -**Describe the bug** - -A clear and concise description of what the bug is. - -**To Reproduce** - -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** - -A clear and concise description of what you expected to happen. - -**Screenshots / Videos** - -If applicable, add screenshots to help explain your problem. - -**Server Version and Plugins** - -If you just run Geyser-Spigot, you can leave this area blank as the next section covers this information. - -If you're running a multi-server instance, or using Geyser Standalone: - -- Give us the exact output from `/version` on all servers involved. Saying "latest" does not help us at all. -- Please list all plugins on all servers involved. - -If this bug occurs on a server you do not control, please fill this in to the best of your knowledge. - -**Geyser Dump** - -If Geyser starts correctly, please also include the link to a dump by using `/geyser dump`. If you use the Standalone GUI, the option can be found under `Commands` => `Dump`. This provides us information about your server that we can use to debug your issue. - -**Minecraft: Bedrock Edition Version** - -The version of your Minecraft: Bedrock Edition client you tested with, along with your device type (e.g. Windows 10, Switch...). - -**Additional Context** - -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..ca83c83f8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,64 @@ +name: Bug report +about: Create a report to help us improve +body: +- type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report for Geyser! Fill out the following form to your best ability to help us fix the problem. + Only use this if you're absolutely sure that you found a bug and can reproduce it. For anything else, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://github.com/GeyserMC/Geyser/wiki/FAQ) or the [Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues). +- type: textarea + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. + validations: + required: true +- type: textarea + attributes: + label: To Reproduce + description: Steps to reproduce this behaviour + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. See error + validations: + required: true +- type: textarea + attributes: + label: Expected behaviour + description: A clear and concise description of what you expected to happen. + validations: + required: true +- type: textarea + attributes: + label: Screenshots / Videos + description: If applicable, add screenshots to help explain your problem. +- type: textarea + attributes: + label: Server Version and Plugins + description: | + If you just run Geyser-Spigot, you can leave this area blank as the next section covers this information. + If you're running a multi-server instance or using Geyser Standalone: + * Give us the exact output from `/version` on all servers involved. Saying "latest" does not help us at all. + * Please list all plugins on all servers involved. + If this bug occurs on a server you do not control, please full this in to the best of your knowledge. +- type: input + attributes: + label: Geyser Dump + description: If Geyser starts correctly, please also include the link to a dump by using `/geyser dump`. If you're using the Standalone GUI, the option can be found under `Commands` => `Dump`. This provides us information about your server that we can use to debug your issue. +- type: input + attributes: + label: Geyser Version + description: What version of Geyser are you running? + placeholder: "For example: 1.2.0-SNAPSHOT (git-master-2d9baf1)" + validations: + required: true +- type: input + attributes: + label: "Minecraft: Bedrock Edition Version" + description: "What version of Minecraft: Bedrock Edition are you using? Leave empty if the bug happens before you can connect with Minecraft: Bedrock Edition." + placeholder: "For example: 1.16.201" +- type: textarea + attributes: + label: Additional Context + description: Add any other context about the problem here diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 92085a35b..a8a5bf95d 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,11 @@ blank_issues_enabled: false contact_links: - - name: GeyserMC Discord - url: http://discord.geysermc.org/ + - name: Common Issues + url: https://github.com/GeyserMC/Geyser/wiki/Common-Issues + about: Check the common issues to see if you are not alone with that issue and see how you can fix them. + - name: Frequently Asked Questions + url: https://github.com/GeyserMC/Geyser/wiki/FAQ + about: Look at the FAQ page for answers for frequently asked questions. + - name: Get help on the GeyserMC Discord server + url: https://discord.gg/geysermc about: If your issue seems like it could possibly be an easy fix due to configuration, please hop on our Discord. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 46653e624..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -**What feature do you want?** -Add a description - -**Alternatives?** -List any alternatives you might have tried diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..64ad937a5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,21 @@ +name: Feature request +about: Suggest an idea for this project +labels: "Feature Request" +body: +- type: markdown + attributes: + value: | + Thanks for taking the time to fill out this feature request for Geyser! Fill out the following form to your best ability to help us understand your feature request and greately improve the change of it getting added. + For anything else than a feature request, use: [our Discord server](https://discord.gg/geysermc), [the FAQ](https://github.com/GeyserMC/Geyser/wiki/FAQ) or [the Common Issues](https://github.com/GeyserMC/Geyser/wiki/Common-Issues). +- type: textarea + attributes: + label: What feature do you want to see added? + description: A clear and concise description of your feature request. + validations: + required: true +- type: textarea + attributes: + label: Are there any alternatives? + description: List any alternatives you might have tried + validations: + required: true \ No newline at end of file From 66867edbc3e81669c64ea752bae631585849b9cc Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 25 Feb 2021 02:46:34 +0100 Subject: [PATCH 095/161] Fixed missing dependency --- connector/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/connector/pom.xml b/connector/pom.xml index 7d08b5c60..6fb2c127c 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -21,6 +21,12 @@ 1.2.0-SNAPSHOT compile + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.10.2 + compile + com.fasterxml.jackson.dataformat jackson-dataformat-yaml From 9e6402c279850e67cec31a66b53c59516d8b62c7 Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Wed, 24 Feb 2021 20:14:01 -0900 Subject: [PATCH 096/161] Fixes --- .../connector/inventory/Inventory.java | 4 ++ .../network/session/GeyserSession.java | 2 + .../BedrockContainerCloseTranslator.java | 25 ++++++----- .../BedrockLecternUpdateTranslator.java | 2 +- .../LecternInventoryTranslator.java | 4 +- .../chest/DoubleChestInventoryTranslator.java | 6 +++ .../java/JavaRespawnTranslator.java | 1 + .../window/JavaCloseWindowTranslator.java | 2 +- .../java/window/JavaOpenWindowTranslator.java | 4 +- .../connector/utils/InventoryUtils.java | 42 ++++++++++++------- 10 files changed, 59 insertions(+), 33 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java index 535a9077a..179801825 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -61,6 +61,10 @@ public class Inventory { @Getter protected short transactionId = 0; + @Getter + @Setter + private boolean pending = false; + protected Inventory(int id, int size) { this("Inventory", id, size); } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 41269ebf1..f2fa98ba4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -136,6 +136,8 @@ public class GeyserSession implements CommandSender { private final PlayerInventory playerInventory; @Setter private Inventory openInventory; + @Setter + private boolean closingInventory; @Setter private InventoryTranslator inventoryTranslator = InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java index 6f0f877c8..d46a51ef1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java @@ -31,6 +31,7 @@ import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; @Translator(packet = ContainerClosePacket.class) @@ -42,19 +43,21 @@ public class BedrockContainerCloseTranslator extends PacketTranslator session.addInventoryTask(() -> { session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR); session.setOpenInventory(null); + session.setClosingInventory(false); }); SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java index b78dc32f2..639996203 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java @@ -36,6 +36,6 @@ public class JavaCloseWindowTranslator extends PacketTranslator InventoryUtils.closeInventory(session, packet.getWindowId())); + session.addInventoryTask(() -> InventoryUtils.closeInventory(session, packet.getWindowId(), true)); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index 2d1f77317..db20dfcd2 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -51,7 +51,7 @@ public class JavaOpenWindowTranslator extends PacketTranslator 0) { + if (translator instanceof DoubleChestInventoryTranslator && !((Container) inventory).isUsingRealBlock()) { GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { - translator.openInventory(session, inventory); - translator.updateInventory(session, inventory); - }, delay, TimeUnit.MILLISECONDS); + session.addInventoryTask(() -> { + Inventory openInv = session.getOpenInventory(); + if (openInv != null && openInv.getId() == inventory.getId()) { + translator.openInventory(session, inventory); + translator.updateInventory(session, inventory); + } + }); + }, 200, TimeUnit.MILLISECONDS); } else { translator.openInventory(session, inventory); translator.updateInventory(session, inventory); @@ -78,7 +86,7 @@ public class InventoryUtils { } } - public static void closeInventory(GeyserSession session, int windowId) { + public static void closeInventory(GeyserSession session, int windowId, boolean confirm) { session.getPlayerInventory().setCursor(GeyserItemStack.EMPTY, session); updateCursor(session); @@ -86,7 +94,9 @@ public class InventoryUtils { if (inventory != null) { InventoryTranslator translator = session.getInventoryTranslator(); translator.closeInventory(session, inventory); - session.setLastWindowCloseTime(System.currentTimeMillis()); + if (confirm && !inventory.isPending() && !(translator instanceof LecternInventoryTranslator)) { + session.setClosingInventory(true); + } } session.setInventoryTranslator(InventoryTranslator.PLAYER_INVENTORY_TRANSLATOR); session.setOpenInventory(null); From 07f36affb91a2f507e06b8dd2eb3202bf76c469e Mon Sep 17 00:00:00 2001 From: AJ Ferguson Date: Thu, 25 Feb 2021 01:41:04 -0900 Subject: [PATCH 097/161] Add barrel as a valid single chest block --- .../translators/chest/SingleChestInventoryTranslator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java index 42b23d5b4..0f3b8ecb9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java @@ -37,10 +37,10 @@ public class SingleChestInventoryTranslator extends ChestInventoryTranslator { public SingleChestInventoryTranslator(int size) { super(size, 27); this.holder = new BlockInventoryHolder("minecraft:chest[facing=north,type=single,waterlogged=false]", ContainerType.CONTAINER, - "minecraft:ender_chest", "minecraft:trapped_chest") { + "minecraft:ender_chest", "minecraft:trapped_chest", "minecraft:barrel") { @Override protected boolean isValidBlock(String[] javaBlockString) { - if (javaBlockString[0].equals("minecraft:ender_chest")) { + if (javaBlockString[0].equals("minecraft:ender_chest") || javaBlockString[0].equals("minecraft:barrel")) { // Can't have double ender chests return true; } From 255c7ac0d2634d91a7319233703f13bf766bfb16 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 25 Feb 2021 12:22:05 -0500 Subject: [PATCH 098/161] Revert "Add barrel as a valid single chest block" Barrels are a bit more complicated to lump in with chests for the time being. It *can* be fixed, but it requires workarounds. This reverts commit 07f36affb91a2f507e06b8dd2eb3202bf76c469e. --- .../translators/chest/SingleChestInventoryTranslator.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java index 0f3b8ecb9..42b23d5b4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/SingleChestInventoryTranslator.java @@ -37,10 +37,10 @@ public class SingleChestInventoryTranslator extends ChestInventoryTranslator { public SingleChestInventoryTranslator(int size) { super(size, 27); this.holder = new BlockInventoryHolder("minecraft:chest[facing=north,type=single,waterlogged=false]", ContainerType.CONTAINER, - "minecraft:ender_chest", "minecraft:trapped_chest", "minecraft:barrel") { + "minecraft:ender_chest", "minecraft:trapped_chest") { @Override protected boolean isValidBlock(String[] javaBlockString) { - if (javaBlockString[0].equals("minecraft:ender_chest") || javaBlockString[0].equals("minecraft:barrel")) { + if (javaBlockString[0].equals("minecraft:ender_chest")) { // Can't have double ender chests return true; } From 9f5a3561807bebeb366a14776d0c9cab1fd3d8e5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 25 Feb 2021 12:54:30 -0500 Subject: [PATCH 099/161] Armor stand fixes (#1270) Armor stands now show armor if invisible. This allows both names and armor to show on an armor stand, and should allow for custom models that use armor stands to show, to an extent. --- .../org/geysermc/connector/entity/Entity.java | 10 +- .../entity/living/ArmorStandEntity.java | 288 ++++++++++++++++-- 2 files changed, 274 insertions(+), 24 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index d41578db4..3ba3c9730 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -273,13 +273,9 @@ public class Entity { metadata.getFlags().setFlag(EntityFlag.SWIMMING, ((xd & 0x10) == 0x10) && metadata.getFlags().getFlag(EntityFlag.SPRINTING)); // Otherwise swimming is enabled on older servers metadata.getFlags().setFlag(EntityFlag.GLIDING, (xd & 0x80) == 0x80); - if ((xd & 0x20) == 0x20) { - // Armour stands are handled in their own class - if (!this.is(ArmorStandEntity.class)) { - metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); - } - } else { - metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false); + // Armour stands are handled in their own class + if (!this.is(ArmorStandEntity.class)) { + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, (xd & 0x20) == 0x20); } // Shield code diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java index e0f4d9a11..5543c3d5d 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java @@ -29,6 +29,9 @@ import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadat import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; +import com.nukkitx.protocol.bedrock.data.inventory.ItemData; +import com.nukkitx.protocol.bedrock.packet.MoveEntityAbsolutePacket; import lombok.Getter; import org.geysermc.connector.entity.LivingEntity; import org.geysermc.connector.entity.type.EntityType; @@ -42,15 +45,68 @@ public class ArmorStandEntity extends LivingEntity { private boolean isInvisible = false; private boolean isSmall = false; + /** + * On Java Edition, armor stands always show their name. Invisibility hides the name on Bedrock. + * By having a second entity, we can allow an invisible entity with the name tag. + * (This lets armor on armor stands still show) + */ + private ArmorStandEntity secondEntity = null; + /** + * Whether this is the primary armor stand that holds the armor and not the name tag. + */ + private boolean primaryEntity = true; + /** + * Whether the entity's position must be updated to included the offset. + * + * This should be true when the Java server marks the armor stand as invisible, but we shrink the entity + * to allow the nametag to appear. Basically: + * - Is visible: this is irrelevant (false) + * - Has armor, no name: false + * - Has armor, has name: false, with a second entity + * - No armor, no name: false + * - No armor, yes name: true + */ + private boolean positionRequiresOffset = false; + /** + * Whether we should update the position of this armor stand after metadata updates. + */ + private boolean positionUpdateRequired = false; + private GeyserSession session; + public ArmorStandEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); } + @Override + public void spawnEntity(GeyserSession session) { + this.session = session; + this.rotation = Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX()); + super.spawnEntity(session); + } + + @Override + public boolean despawnEntity(GeyserSession session) { + if (secondEntity != null) { + secondEntity.despawnEntity(session); + } + return super.despawnEntity(session); + } + + @Override + public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { + if (secondEntity != null) { + secondEntity.moveRelative(session, relX, relY, relZ, rotation, isOnGround); + } + super.moveRelative(session, relX, relY, relZ, rotation, isOnGround); + } + @Override public void moveAbsolute(GeyserSession session, Vector3f position, Vector3f rotation, boolean isOnGround, boolean teleported) { - // Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands - if (!isMarker && isInvisible && passengers.isEmpty()) { - position = position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d); + if (secondEntity != null) { + secondEntity.moveAbsolute(session, applyOffsetToPosition(position), rotation, isOnGround, teleported); + } else if (positionRequiresOffset) { + // Fake the height to be above where it is so the nametag appears in the right location for invisible non-marker armour stands + position = applyOffsetToPosition(position); } super.moveAbsolute(session, position, Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX()), isOnGround, teleported); @@ -58,47 +114,245 @@ public class ArmorStandEntity extends LivingEntity { @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { + super.updateBedrockMetadata(entityMetadata, session); if (entityMetadata.getId() == 0 && entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); // Check if the armour stand is invisible and store accordingly - if ((xd & 0x20) == 0x20) { - metadata.put(EntityData.SCALE, 0.0f); - isInvisible = true; + if (primaryEntity) { + isInvisible = (xd & 0x20) == 0x20; + updateSecondEntityStatus(false); } + } else if (entityMetadata.getId() == 2 || entityMetadata.getId() == 3) { + updateSecondEntityStatus(false); } else if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); // isSmall - if ((xd & 0x01) == 0x01) { - isSmall = true; + boolean newIsSmall = (xd & 0x01) == 0x01; + if ((newIsSmall != isSmall) && positionRequiresOffset) { + // Fix new inconsistency with offset + this.position = fixOffsetForSize(position, newIsSmall); + positionUpdateRequired = true; + } + isSmall = newIsSmall; + if (isSmall) { - if (metadata.getFloat(EntityData.SCALE) != 0.55f && metadata.getFloat(EntityData.SCALE) != 0.0f) { + float scale = metadata.getFloat(EntityData.SCALE); + if (scale != 0.55f && scale != 0.0f) { metadata.put(EntityData.SCALE, 0.55f); } - if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.5f)) { + if (metadata.getFloat(EntityData.BOUNDING_BOX_WIDTH) == 0.5f) { metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.25f); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.9875f); } - } else if (metadata.get(EntityData.BOUNDING_BOX_WIDTH) != null && metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.25f)) { + } else if (metadata.getFloat(EntityData.BOUNDING_BOX_WIDTH) == 0.25f) { metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight()); } // setMarker - if ((xd & 0x10) == 0x10 && (metadata.get(EntityData.BOUNDING_BOX_WIDTH) == null || !metadata.get(EntityData.BOUNDING_BOX_WIDTH).equals(0.0f))) { + boolean oldIsMarker = isMarker; + isMarker = (xd & 0x10) == 0x10; + if (isMarker) { metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); - isMarker = true; + } + if (oldIsMarker != isMarker) { + updateSecondEntityStatus(false); } } - super.updateBedrockMetadata(entityMetadata, session); + if (secondEntity != null) { + secondEntity.updateBedrockMetadata(entityMetadata, session); + } } @Override - public void spawnEntity(GeyserSession session) { - this.rotation = Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX()); - super.spawnEntity(session); + public void updateBedrockMetadata(GeyserSession session) { + if (secondEntity != null) { + secondEntity.updateBedrockMetadata(session); + } + super.updateBedrockMetadata(session); + if (positionUpdateRequired) { + positionUpdateRequired = false; + updatePosition(); + } + } + + @Override + public void setHelmet(ItemData helmet) { + super.setHelmet(helmet); + updateSecondEntityStatus(true); + } + + @Override + public void setChestplate(ItemData chestplate) { + super.setChestplate(chestplate); + updateSecondEntityStatus(true); + } + + @Override + public void setLeggings(ItemData leggings) { + super.setLeggings(leggings); + updateSecondEntityStatus(true); + } + + @Override + public void setBoots(ItemData boots) { + super.setBoots(boots); + updateSecondEntityStatus(true); + } + + @Override + public void setHand(ItemData hand) { + super.setHand(hand); + updateSecondEntityStatus(true); + } + + @Override + public void setOffHand(ItemData offHand) { + super.setOffHand(offHand); + updateSecondEntityStatus(true); + } + + /** + * Determine if we need to load or unload the second entity. + * + * @param sendMetadata whether to send a metadata update after a change. + */ + private void updateSecondEntityStatus(boolean sendMetadata) { + // A secondary entity always has to have the offset applied, so it remains invisible and the nametag shows. + if (!primaryEntity) return; + if (!isInvisible || isMarker) { + // It is either impossible to show armor, or the armor stand isn't invisible. We good. + updateOffsetRequirement(false); + if (positionUpdateRequired) { + positionUpdateRequired = false; + updatePosition(); + } + + if (secondEntity != null) { + secondEntity.despawnEntity(session); + secondEntity = null; + } + return; + } + //boolean isNametagEmpty = metadata.getString(EntityData.NAMETAG).isEmpty() || metadata.getByte(EntityData.NAMETAG_ALWAYS_SHOW, (byte) -1) == (byte) 0; - may not be necessary? + boolean isNametagEmpty = metadata.getString(EntityData.NAMETAG).isEmpty(); + if (!isNametagEmpty && (!helmet.equals(ItemData.AIR) || !chestplate.equals(ItemData.AIR) || !leggings.equals(ItemData.AIR) + || !boots.equals(ItemData.AIR) || !hand.equals(ItemData.AIR) || !offHand.equals(ItemData.AIR))) { + // If the second entity exists, no need to recreate it. + // We can't stuff this check above or else it'll fall into another else case and delete the second entity + if (secondEntity != null) return; + + // Create the second entity. It doesn't need to worry about the items, but it does need to worry about + // the metadata as it will hold the name tag. + secondEntity = new ArmorStandEntity(0, session.getEntityCache().getNextEntityId().incrementAndGet(), + EntityType.ARMOR_STAND, position, motion, rotation); + secondEntity.primaryEntity = false; + if (!this.positionRequiresOffset) { + // Ensure the offset is applied for the 0 scale + secondEntity.position = secondEntity.applyOffsetToPosition(secondEntity.position); + } + // Copy metadata + secondEntity.isSmall = isSmall; + secondEntity.getMetadata().putAll(metadata); + // Copy the flags so they aren't the same object in memory + secondEntity.getMetadata().putFlags(metadata.getFlags().copy()); + // Guarantee this copy is NOT invisible + secondEntity.getMetadata().getFlags().setFlag(EntityFlag.INVISIBLE, false); + // Scale to 0 to show nametag + secondEntity.getMetadata().put(EntityData.SCALE, 0.0f); + // No bounding box as we don't want to interact with this entity + secondEntity.getMetadata().put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); + secondEntity.getMetadata().put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); + secondEntity.spawnEntity(session); + + // Reset scale of the proper armor stand + this.metadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f); + // Set the proper armor stand to invisible to show armor + this.metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); + // Update the position of the armor stand + updateOffsetRequirement(false); + } else if (isNametagEmpty) { + // We can just make an invisible entity + // Reset scale of the proper armor stand + metadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f); + // Set the proper armor stand to invisible to show armor + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, true); + // Update offset + updateOffsetRequirement(false); + + if (secondEntity != null) { + secondEntity.despawnEntity(session); + secondEntity = null; + } + } else { + // Nametag is not empty and there is no armor + // We don't need to make a new entity + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false); + metadata.put(EntityData.SCALE, 0.0f); + // As the above is applied, we need an offset + updateOffsetRequirement(true); + + if (secondEntity != null) { + secondEntity.despawnEntity(session); + secondEntity = null; + } + } + if (sendMetadata) { + this.updateBedrockMetadata(session); + } + } + + /** + * @return the selected position with the position offset applied. + */ + private Vector3f applyOffsetToPosition(Vector3f position) { + return position.add(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d); + } + + /** + * @return an adjusted offset for the new small status. + */ + private Vector3f fixOffsetForSize(Vector3f position, boolean isNowSmall) { + position = removeOffsetFromPosition(position); + return position.add(0d, entityType.getHeight() * (isNowSmall ? 0.55d : 1d), 0d); + } + + /** + * @return the selected position with the position offset removed. + */ + private Vector3f removeOffsetFromPosition(Vector3f position) { + return position.sub(0d, entityType.getHeight() * (isSmall ? 0.55d : 1d), 0d); + } + + /** + * Set the offset to a new value; if it changed, update the position, too. + */ + private void updateOffsetRequirement(boolean newValue) { + if (newValue != positionRequiresOffset) { + this.positionRequiresOffset = newValue; + if (positionRequiresOffset) { + this.position = applyOffsetToPosition(position); + } else { + this.position = removeOffsetFromPosition(position); + } + positionUpdateRequired = true; + } + } + + /** + * Updates position without calling movement code. + */ + private void updatePosition() { + MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); + moveEntityPacket.setRuntimeEntityId(geyserId); + moveEntityPacket.setPosition(position); + moveEntityPacket.setRotation(Vector3f.from(rotation.getX(), rotation.getX(), rotation.getX())); + moveEntityPacket.setOnGround(onGround); + moveEntityPacket.setTeleported(false); + session.sendUpstreamPacket(moveEntityPacket); } } From 325b8ab4d42e660e0d63f316ca245fe73f17f775 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 25 Feb 2021 13:12:25 -0500 Subject: [PATCH 100/161] Fix rowing from Java to Bedrock (#1943) Rowing apparently broke in possibly the 1.16 update and nobody noticed until now. --- .../geysermc/connector/entity/BoatEntity.java | 77 +++++++++++++------ 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java index ed5272b89..d07ecc965 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/BoatEntity.java @@ -28,6 +28,7 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.packet.AnimatePacket; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; @@ -105,27 +106,38 @@ public class BoatEntity extends Entity { metadata.put(EntityData.VARIANT, entityMetadata.getValue()); } else if (entityMetadata.getId() == 11) { isPaddlingLeft = (boolean) entityMetadata.getValue(); - if (!isPaddlingLeft) { - metadata.put(EntityData.ROW_TIME_LEFT, 0f); - } - else { + if (isPaddlingLeft) { // Java sends simply "true" and "false" (is_paddling_left), Bedrock keeps sending packets as you're rowing // This is an asynchronous method that emulates Bedrock rowing until "false" is sent. paddleTimeLeft = 0f; - session.getConnector().getGeneralThreadPool().execute(() -> - updateLeftPaddle(session, entityMetadata) - ); + if (!this.passengers.isEmpty()) { + // Get the entity by the first stored passenger and convey motion in this manner + Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong()); + if (entity != null) { + session.getConnector().getGeneralThreadPool().execute(() -> + updateLeftPaddle(session, entity) + ); + } + } + } else { + // Indicate that the row position should be reset + metadata.put(EntityData.ROW_TIME_LEFT, 0.0f); } } else if (entityMetadata.getId() == 12) { isPaddlingRight = (boolean) entityMetadata.getValue(); - if (!isPaddlingRight) { - metadata.put(EntityData.ROW_TIME_RIGHT, 0f); - } else { + if (isPaddlingRight) { paddleTimeRight = 0f; - session.getConnector().getGeneralThreadPool().execute(() -> - updateRightPaddle(session, entityMetadata) - ); + if (!this.passengers.isEmpty()) { + Entity entity = session.getEntityCache().getEntityByJavaId(this.passengers.iterator().nextLong()); + if (entity != null) { + session.getConnector().getGeneralThreadPool().execute(() -> + updateRightPaddle(session, entity) + ); + } + } + } else { + metadata.put(EntityData.ROW_TIME_RIGHT, 0.0f); } } else if (entityMetadata.getId() == 13) { // Possibly - I don't think this does anything? @@ -135,27 +147,46 @@ public class BoatEntity extends Entity { super.updateBedrockMetadata(entityMetadata, session); } - public void updateLeftPaddle(GeyserSession session, EntityMetadata entityMetadata) { + @Override + public void updateBedrockMetadata(GeyserSession session) { + super.updateBedrockMetadata(session); + + // As these indicate to reset rowing, remove them until it is time to send them out again. + metadata.remove(EntityData.ROW_TIME_LEFT); + metadata.remove(EntityData.ROW_TIME_RIGHT); + } + + private void updateLeftPaddle(GeyserSession session, Entity rower) { if (isPaddlingLeft) { paddleTimeLeft += ROWING_SPEED; - metadata.put(EntityData.ROW_TIME_LEFT, paddleTimeLeft); - super.updateBedrockMetadata(entityMetadata, session); + sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_LEFT, paddleTimeLeft); + session.getConnector().getGeneralThreadPool().schedule(() -> - updateLeftPaddle(session, entityMetadata), + updateLeftPaddle(session, rower), 100, TimeUnit.MILLISECONDS ); - }} + } + } - public void updateRightPaddle(GeyserSession session, EntityMetadata entityMetadata) { + private void updateRightPaddle(GeyserSession session, Entity rower) { if (isPaddlingRight) { paddleTimeRight += ROWING_SPEED; - metadata.put(EntityData.ROW_TIME_RIGHT, paddleTimeRight); - super.updateBedrockMetadata(entityMetadata, session); + sendAnimationPacket(session, rower, AnimatePacket.Action.ROW_RIGHT, paddleTimeRight); + session.getConnector().getGeneralThreadPool().schedule(() -> - updateRightPaddle(session, entityMetadata), + updateRightPaddle(session, rower), 100, TimeUnit.MILLISECONDS ); - }} + } + } + + private void sendAnimationPacket(GeyserSession session, Entity rower, AnimatePacket.Action action, float rowTime) { + AnimatePacket packet = new AnimatePacket(); + packet.setRuntimeEntityId(rower.getGeyserId()); + packet.setAction(action); + packet.setRowingTime(rowTime); + session.sendUpstreamPacket(packet); + } } From ae3f50a79c4d5fe6e276bc940d80f43f7647e808 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 25 Feb 2021 13:13:24 -0500 Subject: [PATCH 101/161] Firework star item translation (#1968) Firework stars, unsurprisingly, share some code with fireworks. This commit adds a new FireworkBaseTranslator abstract class that both firework items extend from, in order to use the explosion translation code. Firework stars also show color. --- .../nbt/FireworkBaseTranslator.java | 124 +++++++++++++ .../nbt/FireworkRocketTranslator.java | 92 ++++++++++ .../nbt/FireworkStarTranslator.java | 96 ++++++++++ .../translators/nbt/FireworkTranslator.java | 164 ------------------ 4 files changed, 312 insertions(+), 164 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkBaseTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkRocketTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkStarTranslator.java delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkBaseTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkBaseTranslator.java new file mode 100644 index 000000000..dff40ea75 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkBaseTranslator.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item.translators.nbt; + +import com.github.steveice10.opennbt.tag.builtin.ByteArrayTag; +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; +import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; +import org.geysermc.connector.utils.FireworkColor; +import org.geysermc.connector.utils.MathUtils; + +/** + * Stores common code for firework rockets and firework stars. + */ +public abstract class FireworkBaseTranslator extends NbtItemStackTranslator { + + protected CompoundTag translateExplosionToBedrock(CompoundTag explosion, String newName) { + CompoundTag newExplosionData = new CompoundTag(newName); + + if (explosion.get("Type") != null) { + newExplosionData.put(new ByteTag("FireworkType", MathUtils.convertByte(explosion.get("Type").getValue()))); + } + + if (explosion.get("Colors") != null) { + int[] oldColors = (int[]) explosion.get("Colors").getValue(); + byte[] colors = new byte[oldColors.length]; + + int i = 0; + for (int color : oldColors) { + colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); + } + + newExplosionData.put(new ByteArrayTag("FireworkColor", colors)); + } + + if (explosion.get("FadeColors") != null) { + int[] oldColors = (int[]) explosion.get("FadeColors").getValue(); + byte[] colors = new byte[oldColors.length]; + + int i = 0; + for (int color : oldColors) { + colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); + } + + newExplosionData.put(new ByteArrayTag("FireworkFade", colors)); + } + + if (explosion.get("Trail") != null) { + newExplosionData.put(new ByteTag("FireworkTrail", MathUtils.convertByte(explosion.get("Trail").getValue()))); + } + + if (explosion.get("Flicker") != null) { + newExplosionData.put(new ByteTag("FireworkFlicker", MathUtils.convertByte(explosion.get("Flicker").getValue()))); + } + + return newExplosionData; + } + + protected CompoundTag translateExplosionToJava(CompoundTag explosion, String newName) { + CompoundTag newExplosionData = new CompoundTag(newName); + + if (explosion.get("FireworkType") != null) { + newExplosionData.put(new ByteTag("Type", MathUtils.convertByte(explosion.get("FireworkType").getValue()))); + } + + if (explosion.get("FireworkColor") != null) { + byte[] oldColors = (byte[]) explosion.get("FireworkColor").getValue(); + int[] colors = new int[oldColors.length]; + + int i = 0; + for (byte color : oldColors) { + colors[i++] = FireworkColor.fromBedrockID(color).getJavaID(); + } + + newExplosionData.put(new IntArrayTag("Colors", colors)); + } + + if (explosion.get("FireworkFade") != null) { + byte[] oldColors = (byte[]) explosion.get("FireworkFade").getValue(); + int[] colors = new int[oldColors.length]; + + int i = 0; + for (byte color : oldColors) { + colors[i++] = FireworkColor.fromBedrockID(color).getJavaID(); + } + + newExplosionData.put(new IntArrayTag("FadeColors", colors)); + } + + if (explosion.get("FireworkTrail") != null) { + newExplosionData.put(new ByteTag("Trail", MathUtils.convertByte(explosion.get("FireworkTrail").getValue()))); + } + + if (explosion.get("FireworkFlicker") != null) { + newExplosionData.put(new ByteTag("Flicker", MathUtils.convertByte(explosion.get("FireworkFlicker").getValue()))); + } + + return newExplosionData; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkRocketTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkRocketTranslator.java new file mode 100644 index 000000000..f294315c8 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkRocketTranslator.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item.translators.nbt; + +import com.github.steveice10.opennbt.tag.builtin.ByteTag; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.utils.MathUtils; + +@ItemRemapper +public class FireworkRocketTranslator extends FireworkBaseTranslator { + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { + CompoundTag fireworks = itemTag.get("Fireworks"); + if (fireworks == null) { + return; + } + + if (fireworks.get("Flight") != null) { + fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); + } + + ListTag explosions = fireworks.get("Explosions"); + if (explosions == null) { + return; + } + for (Tag effect : explosions.getValue()) { + CompoundTag effectData = (CompoundTag) effect; + CompoundTag newEffectData = translateExplosionToBedrock(effectData, ""); + + explosions.remove(effectData); + explosions.add(newEffectData); + } + } + + @Override + public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + CompoundTag fireworks = itemTag.get("Fireworks"); + if (fireworks == null) { + return; + } + + if (fireworks.contains("Flight")) { + fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); + } + + ListTag explosions = fireworks.get("Explosions"); + if (explosions == null) { + return; + } + for (Tag effect : explosions.getValue()) { + CompoundTag effectData = (CompoundTag) effect; + CompoundTag newEffectData = translateExplosionToJava(effectData, ""); + + explosions.remove(effect); + explosions.add(newEffectData); + } + } + + @Override + public boolean acceptItem(ItemEntry itemEntry) { + return "minecraft:firework_rocket".equals(itemEntry.getJavaIdentifier()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkStarTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkStarTranslator.java new file mode 100644 index 000000000..686887b45 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkStarTranslator.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.item.translators.nbt; + +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntArrayTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.ItemRemapper; +import org.geysermc.connector.network.translators.item.ItemEntry; + +@ItemRemapper +public class FireworkStarTranslator extends FireworkBaseTranslator { + + @Override + public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { + Tag explosion = itemTag.get("Explosion"); + if (explosion instanceof CompoundTag) { + CompoundTag newExplosion = translateExplosionToBedrock((CompoundTag) explosion, "FireworksItem"); + itemTag.remove("Explosion"); + itemTag.put(newExplosion); + Tag color = ((CompoundTag) explosion).get("Colors"); + if (color instanceof IntArrayTag) { + // Determine the custom color, if any. + // Mostly replicates Java's own rendering code, as Java determines the final firework star color client-side + // while Bedrock determines it server-side. + int[] colors = ((IntArrayTag) color).getValue(); + if (colors.length == 0) { + return; + } + int finalColor; + if (colors.length == 1) { + finalColor = colors[0]; + } else { + int r = 0; + int g = 0; + int b = 0; + + for (int fireworkColor : colors) { + r += (fireworkColor & (255 << 16)) >> 16; + g += (fireworkColor & (255 << 8)) >> 8; + b += fireworkColor & 255; + } + + r /= colors.length; + g /= colors.length; + b /= colors.length; + finalColor = r << 16 | g << 8 | b; + } + + itemTag.put(new IntTag("customColor", finalColor)); + } + } + } + + @Override + public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { + Tag explosion = itemTag.get("FireworksItem"); + if (explosion instanceof CompoundTag) { + CompoundTag newExplosion = translateExplosionToJava((CompoundTag) explosion, "Explosion"); + itemTag.remove("FireworksItem"); + itemTag.put(newExplosion); + } + // Remove custom color, if any, since this only exists on Bedrock + itemTag.remove("customColor"); + } + + @Override + public boolean acceptItem(ItemEntry itemEntry) { + return "minecraft:firework_star".equals(itemEntry.getJavaIdentifier()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java deleted file mode 100644 index 8c5b74f13..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/FireworkTranslator.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.translators.item.translators.nbt; - -import com.github.steveice10.opennbt.tag.builtin.*; -import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.ItemRemapper; -import org.geysermc.connector.network.translators.item.ItemEntry; -import org.geysermc.connector.network.translators.item.NbtItemStackTranslator; -import org.geysermc.connector.utils.FireworkColor; -import org.geysermc.connector.utils.MathUtils; - -@ItemRemapper -public class FireworkTranslator extends NbtItemStackTranslator { - - @Override - public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("Fireworks")) { - return; - } - - CompoundTag fireworks = itemTag.get("Fireworks"); - if (fireworks.get("Flight") != null) { - fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); - } - - ListTag explosions = fireworks.get("Explosions"); - if (explosions == null) { - return; - } - for (Tag effect : explosions.getValue()) { - CompoundTag effectData = (CompoundTag) effect; - - CompoundTag newEffectData = new CompoundTag(""); - - if (effectData.get("Type") != null) { - newEffectData.put(new ByteTag("FireworkType", MathUtils.convertByte(effectData.get("Type").getValue()))); - } - - if (effectData.get("Colors") != null) { - int[] oldColors = (int[]) effectData.get("Colors").getValue(); - byte[] colors = new byte[oldColors.length]; - - int i = 0; - for (int color : oldColors) { - colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); - } - - newEffectData.put(new ByteArrayTag("FireworkColor", colors)); - } - - if (effectData.get("FadeColors") != null) { - int[] oldColors = (int[]) effectData.get("FadeColors").getValue(); - byte[] colors = new byte[oldColors.length]; - - int i = 0; - for (int color : oldColors) { - colors[i++] = FireworkColor.fromJavaID(color).getBedrockID(); - } - - newEffectData.put(new ByteArrayTag("FireworkFade", colors)); - } - - if (effectData.get("Trail") != null) { - newEffectData.put(new ByteTag("FireworkTrail", MathUtils.convertByte(effectData.get("Trail").getValue()))); - } - - if (effectData.get("Flicker") != null) { - newEffectData.put(new ByteTag("FireworkFlicker", MathUtils.convertByte(effectData.get("Flicker").getValue()))); - } - - explosions.remove(effect); - explosions.add(newEffectData); - } - } - - @Override - public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("Fireworks")) { - return; - } - CompoundTag fireworks = itemTag.get("Fireworks"); - if (fireworks.contains("Flight")) { - fireworks.put(new ByteTag("Flight", MathUtils.convertByte(fireworks.get("Flight").getValue()))); - } - - if (!itemTag.contains("Explosions")) { - return; - } - ListTag explosions = fireworks.get("Explosions"); - for (Tag effect : explosions.getValue()) { - CompoundTag effectData = (CompoundTag) effect; - - CompoundTag newEffectData = new CompoundTag(""); - - if (effectData.get("FireworkType") != null) { - newEffectData.put(new ByteTag("Type", MathUtils.convertByte(effectData.get("FireworkType").getValue()))); - } - - if (effectData.get("FireworkColor") != null) { - byte[] oldColors = (byte[]) effectData.get("FireworkColor").getValue(); - int[] colors = new int[oldColors.length]; - - int i = 0; - for (byte color : oldColors) { - colors[i++] = FireworkColor.fromBedrockID(color).getJavaID(); - } - - newEffectData.put(new IntArrayTag("Colors", colors)); - } - - if (effectData.get("FireworkFade") != null) { - byte[] oldColors = (byte[]) effectData.get("FireworkFade").getValue(); - int[] colors = new int[oldColors.length]; - - int i = 0; - for (byte color : oldColors) { - colors[i++] = FireworkColor.fromBedrockID(color).getJavaID(); - } - - newEffectData.put(new IntArrayTag("FadeColors", colors)); - } - - if (effectData.get("FireworkTrail") != null) { - newEffectData.put(new ByteTag("Trail", MathUtils.convertByte(effectData.get("FireworkTrail").getValue()))); - } - - if (effectData.get("FireworkFlicker") != null) { - newEffectData.put(new ByteTag("Flicker", MathUtils.convertByte(effectData.get("FireworkFlicker").getValue()))); - } - - explosions.remove(effect); - explosions.add(newEffectData); - } - } - - @Override - public boolean acceptItem(ItemEntry itemEntry) { - return "minecraft:firework_rocket".equals(itemEntry.getJavaIdentifier()); - } -} From 0832e7d65cfe8c4abb6348ac25eef679f3efa912 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Thu, 25 Feb 2021 20:55:00 +0100 Subject: [PATCH 102/161] Fixes issue when both Geyser and Floodgate are on the same server --- bootstrap/spigot/src/main/resources/plugin.yml | 2 +- .../src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/spigot/src/main/resources/plugin.yml b/bootstrap/spigot/src/main/resources/plugin.yml index fee71ab1f..ea655f902 100644 --- a/bootstrap/spigot/src/main/resources/plugin.yml +++ b/bootstrap/spigot/src/main/resources/plugin.yml @@ -3,7 +3,7 @@ name: ${outputName}-Spigot author: ${project.organization.name} website: ${project.organization.url} version: ${project.version} -softdepend: ["ViaVersion"] +softdepend: ["ViaVersion", "floodgate"] api-version: 1.13 commands: geyser: diff --git a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java index 950f0eb55..7d67a44a0 100644 --- a/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java +++ b/common/src/main/java/org/geysermc/floodgate/util/LinkedPlayer.java @@ -57,7 +57,7 @@ public final class LinkedPlayer implements Cloneable { return new LinkedPlayer(javaUsername, javaUniqueId, bedrockId); } - static LinkedPlayer fromString(String data) { + public static LinkedPlayer fromString(String data) { String[] split = data.split(";"); if (split.length != 3) { return null; From 072700888195a7ba33afd17a8745aa52b0fa3448 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 25 Feb 2021 16:12:58 -0500 Subject: [PATCH 103/161] Villager fixes + other stuff - Consoles trading with villagers should work better - Villagers closing their inventory work now --- .../BedrockContainerCloseTranslator.java | 7 +++++- .../inventory/InventoryTranslator.java | 25 +++++++++++-------- .../MerchantInventoryTranslator.java | 8 ++++++ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java index d46a51ef1..c2e0618ba 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java @@ -28,10 +28,10 @@ package org.geysermc.connector.network.translators.bedrock; import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket; import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.MerchantContainer; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; @Translator(packet = ContainerClosePacket.class) @@ -47,6 +47,11 @@ public class BedrockContainerCloseTranslator extends PacketTranslator Date: Thu, 25 Feb 2021 18:17:43 -0500 Subject: [PATCH 104/161] Fix closing the inventory from the server --- .../java/window/JavaCloseWindowTranslator.java | 2 +- .../geysermc/connector/utils/InventoryUtils.java | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java index 639996203..13295e424 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java @@ -36,6 +36,6 @@ public class JavaCloseWindowTranslator extends PacketTranslator InventoryUtils.closeInventory(session, packet.getWindowId(), true)); + session.addInventoryTask(() -> InventoryUtils.closeInventory(session, packet.getWindowId(), (session.getOpenInventory() != null && session.getOpenInventory().getId() == packet.getWindowId()))); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java index cf235b4c7..9588333d1 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -39,7 +39,10 @@ import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.common.ChatColor; -import org.geysermc.connector.inventory.*; +import org.geysermc.connector.inventory.Container; +import org.geysermc.connector.inventory.GeyserItemStack; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.inventory.translators.LecternInventoryTranslator; @@ -70,19 +73,21 @@ public class InventoryUtils { if (translator != null) { translator.prepareInventory(session, inventory); if (translator instanceof DoubleChestInventoryTranslator && !((Container) inventory).isUsingRealBlock()) { - GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { + GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> session.addInventoryTask(() -> { Inventory openInv = session.getOpenInventory(); if (openInv != null && openInv.getId() == inventory.getId()) { translator.openInventory(session, inventory); translator.updateInventory(session, inventory); } - }); - }, 200, TimeUnit.MILLISECONDS); + }), 200, TimeUnit.MILLISECONDS); } else { translator.openInventory(session, inventory); translator.updateInventory(session, inventory); } + } else { + // Precaution - as of 1.16 every inventory should be translated so this shouldn't happen + session.setOpenInventory(null); } } From acbcf4baf32f3cf21730cbe3aa1270d88ae2d25b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 25 Feb 2021 21:51:50 -0500 Subject: [PATCH 105/161] Fix shulker boxes resetting data and set all shulker boxes as valid --- .../holder/BlockInventoryHolder.java | 6 ++-- .../AbstractBlockInventoryTranslator.java | 11 +++++++ .../ShulkerInventoryTranslator.java | 33 ++++++++++++++++++- .../ShulkerBoxBlockEntityTranslator.java | 8 ++++- 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index 3186a85e9..8ea24053a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -82,7 +82,7 @@ public class BlockInventoryHolder extends InventoryHolder { // We can safely use this block inventory.setHolderPosition(session.getLastInteractionBlockPosition()); ((Container) inventory).setUsingRealBlock(true, javaBlockString[0]); - setCustomName(session, session.getLastInteractionBlockPosition(), inventory); + setCustomName(session, session.getLastInteractionBlockPosition(), inventory, javaBlockId); return; } } @@ -98,7 +98,7 @@ public class BlockInventoryHolder extends InventoryHolder { session.sendUpstreamPacket(blockPacket); inventory.setHolderPosition(position); - setCustomName(session, position, inventory); + setCustomName(session, position, inventory, defaultJavaBlockState); } /** @@ -108,7 +108,7 @@ public class BlockInventoryHolder extends InventoryHolder { return this.validBlocks.contains(javaBlockString[0]); } - protected void setCustomName(GeyserSession session, Vector3i position, Inventory inventory) { + protected void setCustomName(GeyserSession session, Vector3i position, Inventory inventory, int javaBlockState) { NbtMap tag = NbtMap.builder() .putInt("x", position.getX()) .putInt("y", position.getY()) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java index 8664dc192..49caef13b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/AbstractBlockInventoryTranslator.java @@ -53,6 +53,17 @@ public abstract class AbstractBlockInventoryTranslator extends BaseInventoryTran this.updater = updater; } + /** + * @param size the amount of slots that the inventory adds alongside the base inventory slots + * @param holder the custom block holder + * @param updater updater + */ + public AbstractBlockInventoryTranslator(int size, InventoryHolder holder, InventoryUpdater updater) { + super(size); + this.holder = holder; + this.updater = updater; + } + @Override public void prepareInventory(GeyserSession session, Inventory inventory) { holder.prepareInventory(this, session, inventory); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/ShulkerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/ShulkerInventoryTranslator.java index 43584df41..76d1cb1cf 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/ShulkerInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/ShulkerInventoryTranslator.java @@ -25,14 +25,45 @@ package org.geysermc.connector.network.translators.inventory.translators; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; +import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; +import org.geysermc.connector.network.translators.world.block.entity.BlockEntityTranslator; public class ShulkerInventoryTranslator extends AbstractBlockInventoryTranslator { public ShulkerInventoryTranslator() { - super(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, ContainerInventoryUpdater.INSTANCE); + super(27, new BlockInventoryHolder("minecraft:shulker_box[facing=north]", ContainerType.CONTAINER) { + private final BlockEntityTranslator shulkerBoxTranslator = BlockEntityTranslator.BLOCK_ENTITY_TRANSLATORS.get("ShulkerBox"); + + @Override + protected boolean isValidBlock(String[] javaBlockString) { + return javaBlockString[0].contains("shulker_box"); + } + + @Override + protected void setCustomName(GeyserSession session, Vector3i position, Inventory inventory, int javaBlockState) { + NbtMapBuilder tag = NbtMap.builder() + .putInt("x", position.getX()) + .putInt("y", position.getY()) + .putInt("z", position.getZ()) + .putString("CustomName", inventory.getTitle()); + // Don't reset facing property + shulkerBoxTranslator.translateTag(tag, null, javaBlockState); + + BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); + dataPacket.setData(tag.build()); + dataPacket.setBlockPosition(position); + session.sendUpstreamPacket(dataPacket); + } + }, ContainerInventoryUpdater.INSTANCE); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java index c09d2f99d..04d58fcce 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/ShulkerBoxBlockEntityTranslator.java @@ -29,10 +29,16 @@ import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.world.block.BlockStateValues; +import javax.annotation.Nullable; + @BlockEntity(name = "ShulkerBox") public class ShulkerBoxBlockEntityTranslator extends BlockEntityTranslator { + /** + * Also used in {@link org.geysermc.connector.network.translators.inventory.translators.ShulkerInventoryTranslator} + * where {@code tag} is passed as null. + */ @Override - public void translateTag(NbtMapBuilder builder, CompoundTag tag, int blockState) { + public void translateTag(NbtMapBuilder builder, @Nullable CompoundTag tag, int blockState) { byte direction = BlockStateValues.getShulkerBoxDirection(blockState); // Just in case... if (direction == -1) { From ae123f883687ddde3c7dd513a36dc33696f3e298 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 26 Feb 2021 00:44:59 -0500 Subject: [PATCH 106/161] [ci skip] Update README --- README.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/README.md b/README.md index 343fca94a..1af88f078 100644 --- a/README.md +++ b/README.md @@ -34,20 +34,9 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - Test Server: `test.geysermc.org` port `25565` for Java and `19132` for Bedrock ## What's Left to be Added/Fixed -- Lecterns - Near-perfect movement (to the point where anticheat on large servers is unlikely to ban you) - Resource pack conversion/CustomModelData - Some Entity Flags -- The Following Inventories - - Enchantment Table (as a proper GUI) - - Beacon - - Cartography Table - - Stonecutter - - Structure Block - - Horse Inventory - - Loom - - Smithing Table - - Grindstone ## What can't be fixed The following things can't be fixed because of Bedrock limitations. They might be fixable in the future, but not as of now. From 13c924f841c95b69bc75212550191a914e213467 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 26 Feb 2021 00:54:05 -0500 Subject: [PATCH 107/161] Make lecterns more reliable on chunk load in Spigot --- .../spigot/world/manager/GeyserSpigotWorldManager.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java index 25546a3ad..99dbc7763 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -182,7 +182,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { @Override public NbtMap getLecternDataAt(GeyserSession session, int x, int y, int z, boolean isChunkLoad) { // Run as a task to prevent async issues - Bukkit.getScheduler().runTask(this.plugin, () -> { + Runnable lecternInfoGet = () -> { Player bukkitPlayer; if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { return; @@ -220,7 +220,13 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { lecternTag.putCompound("book", bookTag.build()); NbtMap blockEntityTag = lecternTag.build(); BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z)); - }); + }; + if (isChunkLoad) { + // Delay to ensure the chunk is sent first, and then the lectern data + Bukkit.getScheduler().runTaskLater(this.plugin, lecternInfoGet, 5); + } else { + Bukkit.getScheduler().runTask(this.plugin, lecternInfoGet); + } return LecternInventoryTranslator.getBaseLecternTag(x, y, z, 0).build(); // Will be updated later } From 7aef6b14416d4d953240e3a7337a82e0b003e41a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 26 Feb 2021 00:59:47 -0500 Subject: [PATCH 108/161] [ci skip] Re-add structure block to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1af88f078..5a126025e 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - Near-perfect movement (to the point where anticheat on large servers is unlikely to ban you) - Resource pack conversion/CustomModelData - Some Entity Flags +- Stonecutter block UI ## What can't be fixed The following things can't be fixed because of Bedrock limitations. They might be fixable in the future, but not as of now. From 9788ed4dc613788e8acf411efa1a88b86c6b6198 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 26 Feb 2021 14:06:00 -0500 Subject: [PATCH 109/161] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a126025e..205162847 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - Near-perfect movement (to the point where anticheat on large servers is unlikely to ban you) - Resource pack conversion/CustomModelData - Some Entity Flags -- Stonecutter block UI +- Structure block UI ## What can't be fixed The following things can't be fixed because of Bedrock limitations. They might be fixable in the future, but not as of now. From ab9501da6989f9e3d43ae903729ff9ccf7d5e815 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 26 Feb 2021 19:03:18 -0500 Subject: [PATCH 110/161] Use the inventory name always for villager inventories Testing not crashing on 1.8 and 1.13.2. --- .../java/world/JavaTradeListTranslator.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java index 1874fc5e5..8a29be913 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java @@ -31,12 +31,11 @@ import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerTrade import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.nbt.NbtType; -import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.UpdateTradePacket; import org.geysermc.connector.entity.Entity; -import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.inventory.MerchantContainer; import org.geysermc.connector.network.session.GeyserSession; @@ -71,16 +70,7 @@ public class JavaTradeListTranslator extends PacketTranslator Date: Fri, 26 Feb 2021 20:19:14 -0500 Subject: [PATCH 111/161] Fix confirming when the server double-sends a window ID --- .../translators/java/window/JavaCloseWindowTranslator.java | 5 ++++- .../translators/java/window/JavaOpenWindowTranslator.java | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java index 13295e424..9efdee7fc 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java @@ -36,6 +36,9 @@ public class JavaCloseWindowTranslator extends PacketTranslator InventoryUtils.closeInventory(session, packet.getWindowId(), (session.getOpenInventory() != null && session.getOpenInventory().getId() == packet.getWindowId()))); + session.addInventoryTask(() -> + // Sometimes the server can request a window close of ID 0... when the window isn't even open + // Don't confirm in this instance + InventoryUtils.closeInventory(session, packet.getWindowId(), (session.getOpenInventory() != null && session.getOpenInventory().getId() == packet.getWindowId()))); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java index db20dfcd2..77701f97e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenWindowTranslator.java @@ -63,7 +63,8 @@ public class JavaOpenWindowTranslator extends PacketTranslator Date: Fri, 26 Feb 2021 21:53:24 -0500 Subject: [PATCH 112/161] Handle no-NBT lecterns --- .../manager/GeyserSpigotWorldManager.java | 18 ++++-- .../network/session/GeyserSession.java | 2 +- .../LecternInventoryTranslator.java | 55 +++++++++++++------ .../geysermc/connector/utils/ChunkUtils.java | 1 - 4 files changed, 52 insertions(+), 24 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java index 99dbc7763..a35dd5fc6 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -203,17 +203,27 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { return; } BookMeta bookMeta = (BookMeta) itemStack.getItemMeta(); - NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, bookMeta.getPageCount()); + // On the count: allow the book to show/open even there are no pages. We know there is a book here, after all, and this matches Java behavior + boolean hasBookPages = bookMeta.getPageCount() > 0; + NbtMapBuilder lecternTag = LecternInventoryTranslator.getBaseLecternTag(x, y, z, hasBookPages ? bookMeta.getPageCount() : 1); lecternTag.putInt("page", lectern.getPage() / 2); NbtMapBuilder bookTag = NbtMap.builder() .putByte("Count", (byte) itemStack.getAmount()) .putShort("Damage", (short) 0) .putString("Name", "minecraft:writable_book"); - List pages = new ArrayList<>(); - for (String page : bookMeta.getPages()) { + List pages = new ArrayList<>(bookMeta.getPageCount()); + if (hasBookPages) { + for (String page : bookMeta.getPages()) { + NbtMapBuilder pageBuilder = NbtMap.builder() + .putString("photoname", "") + .putString("text", page); + pages.add(pageBuilder.build()); + } + } else { + // Empty page NbtMapBuilder pageBuilder = NbtMap.builder() .putString("photoname", "") - .putString("text", page); + .putString("text", ""); pages.add(pageBuilder.build()); } bookTag.putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, pages).build()); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index f2fa98ba4..393d6275f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -175,7 +175,7 @@ public class GeyserSession implements CommandSender { * See {@link org.geysermc.connector.network.translators.world.WorldManager#getLecternDataAt(GeyserSession, int, int, int, boolean)} * for more information. */ - private final List lecternCache = new ArrayList<>(); + private final Set lecternCache = new ObjectOpenHashSet<>(); @Setter private boolean droppingLecternBook; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java index 05529da3e..084c4fc17 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java @@ -33,6 +33,7 @@ import com.github.steveice10.opennbt.tag.builtin.ListTag; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; +import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; @@ -43,6 +44,8 @@ import org.geysermc.connector.network.translators.inventory.updater.InventoryUpd import org.geysermc.connector.utils.BlockEntityUtils; import org.geysermc.connector.utils.InventoryUtils; +import java.util.Collections; + public class LecternInventoryTranslator extends BaseInventoryTranslator { private final InventoryUpdater updater; @@ -96,11 +99,12 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { // If the method returns true, this is already handled for us GeyserItemStack geyserItemStack = inventory.getItem(0); CompoundTag tag = geyserItemStack.getNbt(); + // Position has to be the last interacted position... right? + Vector3i position = session.getLastInteractionBlockPosition(); + // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet + boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); + NbtMap blockEntityTag; if (tag != null) { - // Position has to be the last interacted position... right? - Vector3i position = session.getLastInteractionBlockPosition(); - // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet - boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); int pagesSize = ((ListTag) tag.get("pages")).size(); ItemData itemData = geyserItemStack.getItemData(session); NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), pagesSize); @@ -111,20 +115,35 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { .putCompound("tag", itemData.getTag()) .build()); lecternTag.putInt("page", lecternContainer.getCurrentBedrockPage()); - NbtMap blockEntityTag = lecternTag.build(); - // Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild - // the block entity tag - lecternContainer.setBlockEntityTag(blockEntityTag); - lecternContainer.setPosition(position); - if (shouldRefresh) { - // Update the lectern because it's not updated client-side - BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position); - session.getLecternCache().add(position); - // Close the window - we will reopen it once the client has this data synced - ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId()); - session.sendDownstreamPacket(closeWindowPacket); - InventoryUtils.closeInventory(session, inventory.getId(), false); - } + blockEntityTag = lecternTag.build(); + } else { + // There is *a* book here, but... no NBT. + NbtMapBuilder lecternTag = getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 1); + NbtMapBuilder bookTag = NbtMap.builder() + .putByte("Count", (byte) 1) + .putShort("Damage", (short) 0) + .putString("Name", "minecraft:writable_book") + .putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, Collections.singletonList( + NbtMap.builder() + .putString("photoname", "") + .putString("text", "") + .build() + )).build()); + + blockEntityTag = lecternTag.putCompound("book", bookTag.build()).build(); + } + // Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild + // the block entity tag + lecternContainer.setBlockEntityTag(blockEntityTag); + lecternContainer.setPosition(position); + if (shouldRefresh) { + // Update the lectern because it's not updated client-side + BlockEntityUtils.updateBlockEntity(session, blockEntityTag, position); + session.getLecternCache().add(position); + // Close the window - we will reopen it once the client has this data synced + ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(lecternContainer.getId()); + session.sendDownstreamPacket(closeWindowPacket); + InventoryUtils.closeInventory(session, inventory.getId(), false); } } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index 1b13d5dea..45785545c 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -401,7 +401,6 @@ public class ChunkUtils { boolean newLecternHasBook = BlockStateValues.getLecternBookStates().get(blockState); if (!session.getConnector().getWorldManager().shouldExpectLecternHandled() && lecternCachedHasBook != newLecternHasBook) { // Refresh the block entirely - it either has a book or no longer has a book - session.getConnector().getLogger().warning("Refreshing lectern entirely"); NbtMap newLecternTag; if (newLecternHasBook) { newLecternTag = session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false); From e7eca7f7b945fdd7148234c7101f5906a4d4c492 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Sat, 27 Feb 2021 13:14:40 +0100 Subject: [PATCH 113/161] Fixed reconnecting to the api --- .../connector/skin/FloodgateSkinUploader.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index 991db08bc..ad3310c8d 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -25,6 +25,7 @@ package org.geysermc.connector.skin; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -38,6 +39,7 @@ import org.geysermc.floodgate.util.WebsocketEventType; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; +import java.net.ConnectException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Iterator; @@ -138,18 +140,21 @@ public final class FloodgateSkinUploader { String error = node.get("error").asText(); logger.info("Got disconnected from the skin uploader: " + error); } - // it can't be something else then info or error, so we won't handle anything other than that. - // try to reconnect (which will make a new id and verify token) after a few seconds - reconnectLater(connector); + } catch (JsonProcessingException ignored) { + // ignore invalid json } catch (Exception e) { logger.error("Error while handling onClose", e); } } + // try to reconnect (which will make a new id and verify token) after a few seconds + reconnectLater(connector); } @Override public void onError(Exception ex) { - logger.error("Got an error", ex); + if (!(ex instanceof ConnectException)) { + logger.error("Got an error", ex); + } } }; } @@ -180,17 +185,10 @@ public final class FloodgateSkinUploader { } private void reconnectLater(GeyserConnector connector) { - //todo doesn't work long additionalTime = ThreadLocalRandom.current().nextInt(7); - connector.getGeneralThreadPool().schedule(() -> { - try { - if (!client.connectBlocking()) { - reconnectLater(connector); - } - } catch (InterruptedException ignored) { - reconnectLater(connector); - } - }, 8 + additionalTime, TimeUnit.SECONDS); + // we don't have to check the result. onClose will handle that for us + connector.getGeneralThreadPool() + .schedule(client::reconnect, 8 + additionalTime, TimeUnit.SECONDS); } public FloodgateSkinUploader start() { From e09f33c61482532c4503b136c0821f084aa0f0ae Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 27 Feb 2021 12:19:30 -0500 Subject: [PATCH 114/161] Clean up and fix more armor stand inconsistencies (#1988) --- .../entity/living/ArmorStandEntity.java | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java index 5543c3d5d..3d1005510 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/ArmorStandEntity.java @@ -123,43 +123,38 @@ public class ArmorStandEntity extends LivingEntity { isInvisible = (xd & 0x20) == 0x20; updateSecondEntityStatus(false); } - } else if (entityMetadata.getId() == 2 || entityMetadata.getId() == 3) { + } else if (entityMetadata.getId() == 2) { updateSecondEntityStatus(false); } else if (entityMetadata.getId() == 14 && entityMetadata.getType() == MetadataType.BYTE) { byte xd = (byte) entityMetadata.getValue(); // isSmall boolean newIsSmall = (xd & 0x01) == 0x01; - if ((newIsSmall != isSmall) && positionRequiresOffset) { - // Fix new inconsistency with offset - this.position = fixOffsetForSize(position, newIsSmall); - positionUpdateRequired = true; - } - isSmall = newIsSmall; - if (isSmall) { - - float scale = metadata.getFloat(EntityData.SCALE); - if (scale != 0.55f && scale != 0.0f) { - metadata.put(EntityData.SCALE, 0.55f); + if (newIsSmall != isSmall) { + if (positionRequiresOffset) { + // Fix new inconsistency with offset + this.position = fixOffsetForSize(position, newIsSmall); + positionUpdateRequired = true; } - if (metadata.getFloat(EntityData.BOUNDING_BOX_WIDTH) == 0.5f) { - metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.25f); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.9875f); + isSmall = newIsSmall; + if (!isMarker) { + toggleSmallStatus(); } - } else if (metadata.getFloat(EntityData.BOUNDING_BOX_WIDTH) == 0.25f) { - metadata.put(EntityData.BOUNDING_BOX_WIDTH, entityType.getWidth()); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, entityType.getHeight()); } // setMarker boolean oldIsMarker = isMarker; isMarker = (xd & 0x10) == 0x10; - if (isMarker) { - metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); - metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); - } if (oldIsMarker != isMarker) { + if (isMarker) { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.0f); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.0f); + metadata.put(EntityData.SCALE, 0f); + } else { + toggleSmallStatus(); + } + updateSecondEntityStatus(false); } } @@ -226,6 +221,7 @@ public class ArmorStandEntity extends LivingEntity { if (!primaryEntity) return; if (!isInvisible || isMarker) { // It is either impossible to show armor, or the armor stand isn't invisible. We good. + metadata.getFlags().setFlag(EntityFlag.INVISIBLE, false); updateOffsetRequirement(false); if (positionUpdateRequired) { positionUpdateRequired = false; @@ -306,6 +302,15 @@ public class ArmorStandEntity extends LivingEntity { } } + /** + * If this armor stand is not a marker, set its bounding box size and scale. + */ + private void toggleSmallStatus() { + metadata.put(EntityData.BOUNDING_BOX_WIDTH, isSmall ? 0.25f : entityType.getWidth()); + metadata.put(EntityData.BOUNDING_BOX_HEIGHT, isSmall ? 0.9875f : entityType.getHeight()); + metadata.put(EntityData.SCALE, isSmall ? 0.55f : 1f); + } + /** * @return the selected position with the position offset applied. */ From 88d4903fc6a62ed2cd5cc4ea014742fb97b9b2a9 Mon Sep 17 00:00:00 2001 From: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> Date: Sun, 28 Feb 2021 16:07:26 +0000 Subject: [PATCH 115/161] Update Protocol lib and fastutil (#1990) * Update protocol lib and fastutil Move away from jitpack since we store each unique snapshot build in the opencollab repo. Also, trove hasn't existed in the network repo for quite some time so the exclusion isn't needed anymore. * Store fastutil version in a property * Store adventure version in a property --- connector/pom.xml | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index 8b1bfe510..33ad39db3 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -12,6 +12,8 @@ 4.1.59.Final + 8.5.2 + 4.5.0 @@ -28,15 +30,11 @@ compile - com.github.CloudburstMC.Protocol + com.nukkitx.protocol bedrock-v422 - 294e7e5 + 2.6.2-20210228.150048-4 compile - - net.sf.trove4j - trove - com.nukkitx.network raknet @@ -44,9 +42,9 @@ - com.github.CloudburstMC.Network + com.nukkitx.network raknet - a94d2dd + 1.6.26-20210217.205834-2 compile @@ -58,61 +56,61 @@ com.nukkitx.fastutil fastutil-int-int-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-int-float-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-long-long-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-object-long-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-int-byte-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-int-double-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-int-boolean-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-object-int-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-object-byte-maps - 8.3.1 + ${fastutil.version} compile com.nukkitx.fastutil fastutil-object-object-maps - 8.3.1 + ${fastutil.version} compile @@ -214,25 +212,25 @@ net.kyori adventure-api - 4.5.0 + ${adventure.version} compile net.kyori adventure-text-serializer-gson - 4.5.0 + ${adventure.version} compile net.kyori adventure-text-serializer-legacy - 4.5.0 + ${adventure.version} compile net.kyori adventure-text-serializer-gson-legacy-impl - 4.5.0 + ${adventure.version} compile From e4e9758950db1a5ce9258b7aaaca6f30efc18b3c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 2 Mar 2021 19:02:34 -0500 Subject: [PATCH 116/161] Fix logging into a server with Fabric Networking API (#1995) * Fix logging into a server with Fabric Networking API Turns out we prevented the response to LoginPluginRequestPacket. Fabric API 0.28 and later will not let a client join without this response. * Switch back to Jitpack just for Protocol to prevent weird errors * Re-add sub protocol check * Simplify check --- connector/pom.xml | 4 ++-- .../geysermc/connector/network/session/GeyserSession.java | 5 +++-- ...Translator.java => JavaLoginPluginRequestTranslator.java} | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) rename connector/src/main/java/org/geysermc/connector/network/translators/java/{JavaLoginPluginMessageTranslator.java => JavaLoginPluginRequestTranslator.java} (92%) diff --git a/connector/pom.xml b/connector/pom.xml index 33ad39db3..1ee651a2a 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -30,9 +30,9 @@ compile - com.nukkitx.protocol + com.github.CloudburstMC.Protocol bedrock-v422 - 2.6.2-20210228.150048-4 + 42da92f compile diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index ac872d909..885a910c1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -42,6 +42,7 @@ import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPositionRotationPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket; +import com.github.steveice10.mc.protocol.packet.login.client.LoginPluginResponsePacket; import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket; import com.github.steveice10.packetlib.BuiltinFlags; import com.github.steveice10.packetlib.Client; @@ -69,10 +70,10 @@ import lombok.Setter; import org.geysermc.common.window.CustomFormWindow; import org.geysermc.common.window.FormWindow; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.command.CommandSender; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.Tickable; import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.inventory.PlayerInventory; @@ -956,7 +957,7 @@ public class GeyserSession implements CommandSender { * @param packet the java edition packet from MCProtocolLib */ public void sendDownstreamPacket(Packet packet) { - if (downstream != null && downstream.getSession() != null && protocol.getSubProtocol().equals(SubProtocol.GAME)) { + if (downstream != null && downstream.getSession() != null && (protocol.getSubProtocol().equals(SubProtocol.GAME) || packet.getClass() == LoginPluginResponsePacket.class)) { downstream.getSession().send(packet); } else { connector.getLogger().debug("Tried to send downstream packet " + packet.getClass().getSimpleName() + " before connected to the server"); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginMessageTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginRequestTranslator.java similarity index 92% rename from connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginMessageTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginRequestTranslator.java index 74583b797..9680188be 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginMessageTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaLoginPluginRequestTranslator.java @@ -33,10 +33,11 @@ import com.github.steveice10.mc.protocol.packet.login.client.LoginPluginResponse import com.github.steveice10.mc.protocol.packet.login.server.LoginPluginRequestPacket; @Translator(packet = LoginPluginRequestPacket.class) -public class JavaLoginPluginMessageTranslator extends PacketTranslator { +public class JavaLoginPluginRequestTranslator extends PacketTranslator { @Override public void translate(LoginPluginRequestPacket packet, GeyserSession session) { // A vanilla client doesn't know any PluginMessage in the Login state, so we don't know any either. + // Note: Fabric Networking API v1 will not let the client log in without sending this session.sendDownstreamPacket( new LoginPluginResponsePacket(packet.getMessageId(), null) ); From 7e773c99706c8f216ab1d19a0c5e00d181f8c4ba Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 2 Mar 2021 19:10:17 -0500 Subject: [PATCH 117/161] Quietly handle unmappable Java items If vanilla can handle them, so can we. --- .../connector/network/translators/item/ItemTranslator.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java index f63df2f7e..60aa0b52c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ItemTranslator.java @@ -125,8 +125,12 @@ public abstract class ItemTranslator { } ItemEntry bedrockItem = ItemRegistry.getItem(stack); + if (bedrockItem == null) { + session.getConnector().getLogger().debug("No matching ItemEntry for " + stack); + return ItemData.AIR; + } - com.github.steveice10.opennbt.tag.builtin.CompoundTag nbt = stack.getNbt() != null ? stack.getNbt().clone() : null; + CompoundTag nbt = stack.getNbt() != null ? stack.getNbt().clone() : null; // This is a fallback for maps with no nbt if (nbt == null && bedrockItem.getJavaIdentifier().equals("minecraft:filled_map")) { From a2d3ccfb2f1bae1da6c1fb799ac2c1ef84f27c4a Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 3 Mar 2021 19:57:52 +0100 Subject: [PATCH 118/161] Whoops. Forgot to update Geyser to match recent global api changes --- .../connector/skin/FloodgateSkinUploader.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java index ad3310c8d..9f1a515a0 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java +++ b/connector/src/main/java/org/geysermc/connector/skin/FloodgateSkinUploader.java @@ -103,16 +103,25 @@ public final class FloodgateSkinUploader { break; case SKIN_UPLOADED: // if Geyser is the only subscriber we have send it to the server manually + // otherwise it's handled by the Floodgate plugin subscribers if (subscribersCount != 1) { break; } String xuid = node.get("xuid").asText(); - String value = node.get("value").asText(); - String signature = node.get("signature").asText(); - GeyserSession session = connector.getPlayerByXuid(xuid); + if (session != null) { + if (!node.get("success").asBoolean()) { + logger.info("Failed to upload skin for " + session.getName()); + return; + } + + JsonNode data = node.get("data"); + + String value = data.get("value").asText(); + String signature = data.get("signature").asText(); + byte[] bytes = (value + '\0' + signature) .getBytes(StandardCharsets.UTF_8); PluginMessageUtils.sendMessage(session, getSkinChannel(), bytes); From cea16270c49eb083c1c1586f119222e83ea92094 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 3 Mar 2021 21:49:42 +0100 Subject: [PATCH 119/161] Fixed typo in artifactId --- connector/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/pom.xml b/connector/pom.xml index 6fb2c127c..107b4144b 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -35,7 +35,7 @@ org.java-websocket - Java-Websocket + Java-WebSocket 1.5.1 From e0e435fdc5d4906ab18e0e18f4259be8ff03413b Mon Sep 17 00:00:00 2001 From: rtm516 Date: Wed, 3 Mar 2021 23:00:29 +0000 Subject: [PATCH 120/161] Fix capes being scaled wrong and invisible skins (#1940) * Fix capes being scaled wrong and invisible skins * Flush old image objects * Remove alpha workaround and fix more scaling issues * Remove unnecessary scale * Reduce diff Co-authored-by: Camotoy <20743703+Camotoy@users.noreply.github.com> --- .../org/geysermc/connector/skin/SkinProvider.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java index c4d4bc486..61c28addb 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java @@ -413,14 +413,23 @@ public class SkinProvider { // if the requested image is a cape if (provider != null) { if (image.getWidth() > 64) { - image = scale(image, 64, 32); + // Prevent weirdly-scaled capes from being cut off + BufferedImage newImage = new BufferedImage(128, 64, BufferedImage.TYPE_INT_ARGB); + Graphics g = newImage.createGraphics(); + g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null); + g.dispose(); + image.flush(); + image = scale(newImage, 64, 32); } } else { // Very rarely, skins can be larger than Minecraft's default. // Bedrock will not render anything above a width of 128. if (image.getWidth() > 128) { - image = scale(image, 128, image.getHeight() / (image.getWidth() / 128)); + // On Height: Scale by the amount we divided width by, or simply cut down to 128 + image = scale(image, 128, image.getHeight() >= 256 ? (image.getHeight() / (image.getWidth() / 128)) : 128); } + + // TODO remove alpha channel } byte[] data = bufferedImageToImageData(image); From f926b83b33f416d2645febfecd131a8cff1788bc Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 4 Mar 2021 11:32:56 -0500 Subject: [PATCH 121/161] Fix capes of a *smaller* size throwing an error (#1998) --- .../java/org/geysermc/connector/skin/SkinProvider.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java index 61c28addb..745a23ebd 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkinProvider.java @@ -412,7 +412,7 @@ public class SkinProvider { // if the requested image is a cape if (provider != null) { - if (image.getWidth() > 64) { + if (image.getWidth() > 64 || image.getHeight() > 32) { // Prevent weirdly-scaled capes from being cut off BufferedImage newImage = new BufferedImage(128, 64, BufferedImage.TYPE_INT_ARGB); Graphics g = newImage.createGraphics(); @@ -420,6 +420,14 @@ public class SkinProvider { g.dispose(); image.flush(); image = scale(newImage, 64, 32); + } else if (image.getWidth() < 64 || image.getHeight() < 32) { + // Bedrock doesn't like smaller-sized capes, either. + BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_ARGB); + Graphics g = newImage.createGraphics(); + g.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null); + g.dispose(); + image.flush(); + image = newImage; } } else { // Very rarely, skins can be larger than Minecraft's default. From ca3f9550dd466adbdd5e1793eb9673624024ab17 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 4 Mar 2021 12:26:16 -0500 Subject: [PATCH 122/161] Remove unused variables; fix opening double chests quickly --- .../geysermc/connector/network/session/GeyserSession.java | 6 ------ .../bedrock/BedrockContainerCloseTranslator.java | 1 - .../bedrock/BedrockInventoryTransactionTranslator.java | 4 ---- .../translators/java/world/JavaTradeListTranslator.java | 7 ++++--- .../java/org/geysermc/connector/utils/InventoryUtils.java | 3 +++ 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 77ca0d6ff..68f6297e1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -250,12 +250,6 @@ public class GeyserSession implements CommandSender { @Setter private Entity ridingVehicleEntity; - @Setter - private long lastWindowCloseTime = 0; - - @Setter - private long lastInteractedVillagerEid; - @Setter private Int2ObjectMap craftingRecipes; private final Set unlockedRecipes; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java index c2e0618ba..21bc1e437 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java @@ -40,7 +40,6 @@ public class BedrockContainerCloseTranslator extends PacketTranslator { - session.setLastWindowCloseTime(0); byte windowId = packet.getId(); //Client wants close confirmation diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 79f658188..440dff571 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -327,10 +327,6 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator tags = new ArrayList<>(); + boolean addExtraTrade = packet.isRegularVillager() && packet.getVillagerLevel() < 5; + List tags = new ArrayList<>(addExtraTrade ? packet.getTrades().length + 1 : packet.getTrades().length); for (int i = 0; i < packet.getTrades().length; i++) { VillagerTrade trade = packet.getTrades()[i]; NbtMapBuilder recipe = NbtMap.builder(); @@ -101,7 +102,7 @@ public class JavaTradeListTranslator extends PacketTranslator
- - bukkit-public - https://repo.md-5.net/content/repositories/public/ - sponge-repo https://repo.spongepowered.org/repository/maven-public/ From f7130d2fe16aa8d660538f9206a780c408e9ab2d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 6 Mar 2021 13:35:17 -0500 Subject: [PATCH 126/161] Update languages submodule (#2002) --- connector/src/main/resources/languages | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/resources/languages b/connector/src/main/resources/languages index bffb5617c..3d3b60de7 160000 --- a/connector/src/main/resources/languages +++ b/connector/src/main/resources/languages @@ -1 +1 @@ -Subproject commit bffb5617c1ecdacc10031c6ec36988a5f04cb5c6 +Subproject commit 3d3b60de724f3f552f351c5f400269fde7598b67 From e4cff743efc777503132e3c938907c27afd1449e Mon Sep 17 00:00:00 2001 From: SupremeMortal <6178101+SupremeMortal@users.noreply.github.com> Date: Sat, 6 Mar 2021 18:40:37 +0000 Subject: [PATCH 127/161] Use authenticated resolver for jenkins builds (#2008) Allows us to cache dependencies for faster resolution whilst building. --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 6564bd1f9..09e88e86e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -35,8 +35,8 @@ pipeline { rtMavenResolver( id: "maven-resolver", serverId: "opencollab-artifactory", - releaseRepo: "release", - snapshotRepo: "snapshot" + releaseRepo: "maven-deploy-release", + snapshotRepo: "maven-deploy-snapshot" ) rtMavenRun( pom: 'pom.xml', From 7e3d51f9ad66492e06b32682e6f291bd2b81d6a5 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 8 Mar 2021 12:33:59 -0500 Subject: [PATCH 128/161] Update mappings --- connector/src/main/resources/mappings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index 07f65c380..abadb6412 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit 07f65c3803dcd3f83358ee574e54bf129cad0840 +Subproject commit abadb6412c22f2f34eded7ea720e04e8bc7d0f22 From 44e9dba7598ee96c77f7a9a1b94e55d9be185dec Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 8 Mar 2021 14:17:31 -0500 Subject: [PATCH 129/161] Reject requests that are unavailable spaces; other things --- .../network/session/GeyserSession.java | 2 -- .../inventory/InventoryTranslator.java | 32 ++++++++++++------- .../CartographyInventoryTranslator.java | 3 +- .../translators/LoomInventoryTranslator.java | 3 +- .../MerchantInventoryTranslator.java | 5 ++- .../chest/ChestInventoryTranslator.java | 10 ++++++ .../window/JavaOpenHorseWindowTranslator.java | 2 +- 7 files changed, 40 insertions(+), 17 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 68f6297e1..cf4ad8095 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -418,8 +418,6 @@ public class GeyserSession implements CommandSender { this.spawned = false; this.loggedIn = false; - connector.getPlayers().forEach(player -> this.emotes.addAll(player.getEmotes())); - // Make a copy to prevent ConcurrentModificationException final List tmpPlayers = new ArrayList<>(connector.getPlayers()); tmpPlayers.forEach(player -> this.emotes.addAll(player.getEmotes())); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index bb0a033ef..1eef679f5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -121,7 +121,8 @@ public abstract class InventoryTranslator { * * @return true if this transfer should be rejected */ - public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaSourceSlot, int javaDestinationSlot) { + public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer, + int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) { return false; } @@ -201,8 +202,9 @@ public abstract class InventoryTranslator { int sourceSlot = bedrockSlotToJava(transferAction.getSource()); int destSlot = bedrockSlotToJava(transferAction.getDestination()); - if (shouldRejectItemPlace(session, inventory, isCursor(transferAction.getSource()) ? -1 : sourceSlot, - isCursor(transferAction.getDestination()) ? -1 : destSlot)) { + if (shouldRejectItemPlace(session, inventory, transferAction.getSource().getContainer(), + isCursor(transferAction.getSource()) ? -1 : sourceSlot, + transferAction.getDestination().getContainer(), isCursor(transferAction.getDestination()) ? -1 : destSlot)) { // This item would not be here in Java return rejectRequest(request, false); } @@ -273,7 +275,7 @@ public abstract class InventoryTranslator { } break; } - case SWAP: { //TODO + case SWAP: { SwapStackRequestActionData swapAction = (SwapStackRequestActionData) action; if (!(checkNetId(session, inventory, swapAction.getSource()) && checkNetId(session, inventory, swapAction.getDestination()))) { session.getConnector().getLogger().error("DEBUG: About to reject SWAP request made by " + session.getName()); @@ -284,23 +286,31 @@ public abstract class InventoryTranslator { return rejectRequest(request); } - if (isCursor(swapAction.getSource()) && isCursor(swapAction.getDestination())) { //??? + int sourceSlot = bedrockSlotToJava(swapAction.getSource()); + int destSlot = bedrockSlotToJava(swapAction.getDestination()); + boolean isSourceCursor = isCursor(swapAction.getSource()); + boolean isDestCursor = isCursor(swapAction.getDestination()); + + if (shouldRejectItemPlace(session, inventory, swapAction.getSource().getContainer(), + isSourceCursor ? -1 : sourceSlot, + swapAction.getDestination().getContainer(), isDestCursor ? -1 : destSlot)) { + // This item would not be here in Java + return rejectRequest(request, false); + } + + if (isSourceCursor && isDestCursor) { //??? return rejectRequest(request); - } else if (isCursor(swapAction.getSource())) { //swap cursor - int destSlot = bedrockSlotToJava(swapAction.getDestination()); + } else if (isSourceCursor) { //swap cursor if (InventoryUtils.canStack(cursor, plan.getItem(destSlot))) { //TODO: cannot simply swap if cursor stacks with slot (temp slot) return rejectRequest(request); } plan.add(Click.LEFT, destSlot); - } else if (isCursor(swapAction.getDestination())) { //swap cursor - int sourceSlot = bedrockSlotToJava(swapAction.getSource()); + } else if (isDestCursor) { //swap cursor if (InventoryUtils.canStack(cursor, plan.getItem(sourceSlot))) { //TODO return rejectRequest(request); } plan.add(Click.LEFT, sourceSlot); } else { - int sourceSlot = bedrockSlotToJava(swapAction.getSource()); - int destSlot = bedrockSlotToJava(swapAction.getDestination()); if (!cursor.isEmpty()) { //TODO: (temp slot) return rejectRequest(request); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java index 2038a5ba0..319d9ec0a 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java @@ -43,7 +43,8 @@ public class CartographyInventoryTranslator extends AbstractBlockInventoryTransl } @Override - public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaSourceSlot, int javaDestinationSlot) { + public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer, + int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) { if (javaDestinationSlot == 0) { // Bedrock Edition can use paper in slot 0 GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java index 3209a161e..befe9ff30 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java @@ -100,7 +100,8 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { } @Override - public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, int javaSourceSlot, int javaDestinationSlot) { + public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer, + int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) { if (javaDestinationSlot != 1) { return false; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java index 34feb40d9..736568868 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/MerchantInventoryTranslator.java @@ -30,7 +30,9 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityDataMap; import com.nukkitx.protocol.bedrock.data.entity.EntityLinkData; -import com.nukkitx.protocol.bedrock.data.inventory.*; +import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; +import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; +import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import com.nukkitx.protocol.bedrock.packet.SetEntityLinkPacket; import org.geysermc.connector.entity.Entity; @@ -128,6 +130,7 @@ public class MerchantInventoryTranslator extends BaseInventoryTranslator { @Override public void openInventory(GeyserSession session, Inventory inventory) { //Handled in JavaTradeListTranslator + //TODO: send a blank inventory here in case the villager doesn't send a TradeList packet } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/ChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/ChestInventoryTranslator.java index 89366e2c0..d54419b82 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/ChestInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/chest/ChestInventoryTranslator.java @@ -41,6 +41,16 @@ public abstract class ChestInventoryTranslator extends BaseInventoryTranslator { this.updater = new ChestInventoryUpdater(paddedSize); } + @Override + public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer, + int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) { + // Reject any item placements that occur in the unusable inventory space + if (bedrockSourceContainer == ContainerSlotType.CONTAINER && javaSourceSlot >= this.size) { + return true; + } + return bedrockDestinationContainer == ContainerSlotType.CONTAINER && javaDestinationSlot >= this.size; + } + @Override public void updateInventory(GeyserSession session, Inventory inventory) { updater.updateInventory(this, session, inventory); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java index 8952ddcb3..5016b6150 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaOpenHorseWindowTranslator.java @@ -61,7 +61,7 @@ public class JavaOpenHorseWindowTranslator extends PacketTranslator acceptedArmors = new ArrayList<>(); + List acceptedArmors = new ArrayList<>(4); for (String identifier : acceptedHorseArmorIdentifiers) { NbtMapBuilder acceptedItemBuilder = NbtMap.builder() .putShort("Aux", Short.MAX_VALUE) From 3f14624d46b5bf423dda5f315cd4a4fd26e641d7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 8 Mar 2021 15:53:47 -0500 Subject: [PATCH 130/161] Fix some villager block trades being unable to stack --- .../java/world/JavaTradeListTranslator.java | 7 +++++ .../world/block/BlockTranslator.java | 27 ++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java index 9ecffe1d1..ac194eca7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java @@ -44,6 +44,7 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import java.util.ArrayList; import java.util.List; @@ -140,6 +141,12 @@ public class JavaTradeListTranslator extends PacketTranslator JAVA_IDENTIFIER_TO_BEDROCK_TAG; + private static final int BLOCK_STATE_VERSION = 17825808; static { @@ -112,6 +118,8 @@ public class BlockTranslator { throw new AssertionError("Unable to get blocks from runtime block states", e); } + JAVA_IDENTIFIER_TO_BEDROCK_TAG = new Object2ObjectOpenHashMap<>(blocksTag.size()); + // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, // as we no longer send a block palette Object2IntMap blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size()); @@ -188,15 +196,19 @@ public class BlockTranslator { BlockStateValues.storeBlockStateValues(entry.getKey(), javaRuntimeId, entry.getValue()); String cleanJavaIdentifier = entry.getKey().split("\\[")[0]; + String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText(); + + boolean javaIdentifierSameAsBedrock = cleanJavaIdentifier.equals(bedrockIdentifier); if (!JAVA_ID_TO_JAVA_IDENTIFIER_MAP.containsValue(cleanJavaIdentifier)) { uniqueJavaId++; JAVA_ID_TO_JAVA_IDENTIFIER_MAP.put(uniqueJavaId, cleanJavaIdentifier); + if (!javaIdentifierSameAsBedrock) { + JAVA_IDENTIFIER_TO_BEDROCK_TAG.put(cleanJavaIdentifier, blockTag); + } } - String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText(); - - if (!cleanJavaIdentifier.equals(bedrockIdentifier)) { + if (!javaIdentifierSameAsBedrock) { JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier); } @@ -393,4 +405,13 @@ public class BlockTranslator { public static String[] getAllBlockIdentifiers() { return JAVA_ID_TO_JAVA_IDENTIFIER_MAP.values().toArray(new String[0]); } + + /** + * @param cleanJavaIdentifier the clean Java identifier of the block to look up + * + * @return the block tag of the block name mapped from Java to Bedrock. + */ + public static NbtMap getBedrockBlockNbt(String cleanJavaIdentifier) { + return JAVA_IDENTIFIER_TO_BEDROCK_TAG.get(cleanJavaIdentifier); + } } From d19bf07b7a74c7a7b5d9204baf2cf599269caf6c Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 8 Mar 2021 16:42:41 -0500 Subject: [PATCH 131/161] Don't allocate the entire block size --- .../network/translators/world/block/BlockTranslator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index e23c56ee5..2dd455958 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -118,7 +118,7 @@ public class BlockTranslator { throw new AssertionError("Unable to get blocks from runtime block states", e); } - JAVA_IDENTIFIER_TO_BEDROCK_TAG = new Object2ObjectOpenHashMap<>(blocksTag.size()); + JAVA_IDENTIFIER_TO_BEDROCK_TAG = new Object2ObjectOpenHashMap<>(); // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, // as we no longer send a block palette From da11cd298c294d5431ee498ba89fac0c6c825a5a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 8 Mar 2021 16:57:31 -0500 Subject: [PATCH 132/161] Address requests --- .../platform/spigot/GeyserSpigotPlugin.java | 2 +- .../GeyserSpigot1_11CraftingListener.java | 21 +++++++++++++------ ...BedrockInventoryTransactionTranslator.java | 7 +++---- .../LecternInventoryTranslator.java | 2 ++ .../translators/LoomInventoryTranslator.java | 3 +++ .../PlayerInventoryTranslator.java | 2 +- .../StonecutterInventoryTranslator.java | 3 +++ .../java/JavaDeclareRecipesTranslator.java | 7 +++++-- .../java/window/JavaSetSlotTranslator.java | 5 ++--- .../java/world/JavaTradeListTranslator.java | 6 ++++++ 10 files changed, 41 insertions(+), 17 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java index 5296af1b0..671ad18cf 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/GeyserSpigotPlugin.java @@ -205,7 +205,7 @@ public class GeyserSpigotPlugin extends JavaPlugin implements GeyserBootstrap { if (isPre1_12) { // Register events needed to send all recipes to the client - Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigot1_11CraftingListener(this, connector), this); + Bukkit.getServer().getPluginManager().registerEvents(new GeyserSpigot1_11CraftingListener(connector), this); } this.getCommand("geyser").setExecutor(new GeyserSpigotCommandExecutor(connector)); diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java index 66fbb0163..2ee6457ac 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigot1_11CraftingListener.java @@ -45,7 +45,6 @@ import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemTranslator; import org.geysermc.connector.network.translators.item.RecipeRegistry; -import org.geysermc.platform.spigot.GeyserSpigotPlugin; import us.myles.ViaVersion.api.Pair; import us.myles.ViaVersion.api.data.MappingData; import us.myles.ViaVersion.api.protocol.Protocol; @@ -70,14 +69,12 @@ public class GeyserSpigot1_11CraftingListener implements Listener { * The list of all protocols from the client's version to 1.13. */ private final List> protocolList; - private final ProtocolVersion version; - public GeyserSpigot1_11CraftingListener(GeyserSpigotPlugin plugin, GeyserConnector connector) { + public GeyserSpigot1_11CraftingListener(GeyserConnector connector) { this.connector = connector; this.mappingData1_12to1_13 = ProtocolRegistry.getProtocol(Protocol1_13To1_12_2.class).getMappingData(); this.protocolList = ProtocolRegistry.getProtocolPath(MinecraftConstants.PROTOCOL_VERSION, ProtocolVersion.v1_13.getVersion()); - this.version = plugin.getServerProtocolVersion(); } @EventHandler @@ -101,13 +98,16 @@ public class GeyserSpigot1_11CraftingListener implements Listener { CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); craftingDataPacket.setCleanRecipes(true); + Iterator recipeIterator = Bukkit.getServer().recipeIterator(); while (recipeIterator.hasNext()) { Recipe recipe = recipeIterator.next(); + Pair outputs = translateToBedrock(session, recipe.getResult()); ItemStack javaOutput = outputs.getKey(); ItemData output = outputs.getValue(); if (output.getId() == 0) continue; // If items make air we don't want that + boolean isNotAllAir = false; // Check for all-air recipes if (recipe instanceof ShapedRecipe) { ShapedRecipe shapedRecipe = (ShapedRecipe) recipe; @@ -119,8 +119,9 @@ public class GeyserSpigot1_11CraftingListener implements Listener { Pair result = translateToBedrock(session, shapedRecipe.getIngredientMap().get((char) ('a' + i))); ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()}); input[i] = result.getValue(); - isNotAllAir = isNotAllAir || input[i].getId() != 0; + isNotAllAir |= input[i].getId() != 0; } + if (!isNotAllAir) continue; UUID uuid = UUID.randomUUID(); // Add recipe to our internal cache @@ -128,6 +129,7 @@ public class GeyserSpigot1_11CraftingListener implements Listener { "", ingredients, javaOutput); session.getCraftingRecipes().put(netId, new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPED, uuid.toString(), data)); + // Add recipe for Bedrock craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(), shapedRecipe.getShape()[0].length(), shapedRecipe.getShape().length, Arrays.asList(input), @@ -136,18 +138,21 @@ public class GeyserSpigot1_11CraftingListener implements Listener { ShapelessRecipe shapelessRecipe = (ShapelessRecipe) recipe; Ingredient[] ingredients = new Ingredient[shapelessRecipe.getIngredientList().size()]; ItemData[] input = new ItemData[shapelessRecipe.getIngredientList().size()]; + for (int i = 0; i < input.length; i++) { Pair result = translateToBedrock(session, shapelessRecipe.getIngredientList().get(i)); ingredients[i] = new Ingredient(new ItemStack[]{result.getKey()}); input[i] = result.getValue(); - isNotAllAir = isNotAllAir || input[i].getId() != 0; + isNotAllAir |= input[i].getId() != 0; } + if (!isNotAllAir) continue; UUID uuid = UUID.randomUUID(); // Add recipe to our internal cache ShapelessRecipeData data = new ShapelessRecipeData("", ingredients, javaOutput); session.getCraftingRecipes().put(netId, new com.github.steveice10.mc.protocol.data.game.recipe.Recipe(RecipeType.CRAFTING_SHAPELESS, uuid.toString(), data)); + // Add recipe for Bedrock craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), Arrays.asList(input), Collections.singletonList(output), uuid, "crafting_table", 0, netId++)); @@ -163,10 +168,13 @@ public class GeyserSpigot1_11CraftingListener implements Listener { if (itemStack.getType().getId() == 0) { return new Pair<>(null, ItemData.AIR); } + int legacyId = (itemStack.getType().getId() << 4) | (itemStack.getData().getData() & 0xFFFF); + if (itemStack.getType().getId() == 355 && itemStack.getData().getData() == (byte) 0) { // Handle bed color since the server will always be pre-1.12 legacyId = (itemStack.getType().getId() << 4) | ((byte) 14 & 0xFFFF); } + // old version -> 1.13 -> 1.13.1 -> 1.14 -> 1.15 -> 1.16 and so on int itemId; if (mappingData1_12to1_13.getItemMappings().containsKey(legacyId)) { @@ -189,6 +197,7 @@ public class GeyserSpigot1_11CraftingListener implements Listener { ItemData finalData = ItemTranslator.translateToBedrock(session, mcItemStack); return new Pair<>(mcItemStack, finalData); } + // Empty slot, most likely return new Pair<>(null, ItemData.AIR); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 440dff571..0f08e4e84 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -48,7 +48,6 @@ import com.nukkitx.protocol.bedrock.packet.*; import org.geysermc.connector.entity.CommandBlockMinecartEntity; import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.ItemFrameEntity; -import org.geysermc.connector.entity.living.merchant.AbstractMerchantEntity; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.network.session.GeyserSession; @@ -87,10 +86,10 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { - if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot()) - return; - if (session.getPlayerInventory().getItemInHand().isEmpty()) + if (session.getPlayerInventory().getHeldItemSlot() != containerAction.getSlot() || + session.getPlayerInventory().getItemInHand().isEmpty()) { return; + } boolean dropAll = worldAction.getToItem().getCount() > 1; ClientPlayerActionPacket dropAllPacket = new ClientPlayerActionPacket( diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java index c1a623ab6..dbbc418ba 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java @@ -103,6 +103,7 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { Vector3i position = session.getLastInteractionBlockPosition(); // shouldRefresh means that we should boot out the client on our side because their lectern GUI isn't updated yet boolean shouldRefresh = !session.getConnector().getWorldManager().shouldExpectLecternHandled() && !session.getLecternCache().contains(position); + NbtMap blockEntityTag; if (tag != null) { int pagesSize = ((ListTag) tag.get("pages")).size(); @@ -132,6 +133,7 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { blockEntityTag = lecternTag.putCompound("book", bookTag.build()).build(); } + // Even with serverside access to lecterns, we don't easily know which lectern this is, so we need to rebuild // the block entity tag lecternContainer.setBlockEntityTag(blockEntityTag); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java index befe9ff30..38758c5f8 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java @@ -128,6 +128,7 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { return rejectRequest(request); } CraftResultsDeprecatedStackRequestActionData craftData = (CraftResultsDeprecatedStackRequestActionData) data; + // Get the patterns compound tag List newBlockEntityTag = craftData.getResultItems()[0].getTag().getList("Patterns", NbtType.COMPOUND); // Get the pattern that the Bedrock client requests - the last pattern in the Patterns list @@ -151,6 +152,7 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { } CompoundTag blockEntityTag = inputCopy.getNbt().get("BlockEntityTag"); CompoundTag javaBannerPattern = BannerTranslator.getJavaBannerPattern(pattern); + if (blockEntityTag != null) { ListTag patternsList = blockEntityTag.get("Patterns"); if (patternsList != null) { @@ -165,6 +167,7 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { blockEntityTag.put(patternsList); inputCopy.getNbt().put(blockEntityTag); } + // Set the new item as the output inventory.setItem(3, inputCopy, session); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java index 50a0ae2c7..e3dbec507 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/PlayerInventoryTranslator.java @@ -107,7 +107,7 @@ public class PlayerInventoryTranslator extends InventoryTranslator { if (session.getGameMode() == GameMode.CREATIVE) { slotPacket.setItem(UNUSUABLE_CRAFTING_SPACE_BLOCK); - }else{ + } else { slotPacket.setItem(ItemTranslator.translateToBedrock(session, inventory.getItem(i).getItemStack())); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java index 4bb5748dc..2acce3a9b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/StonecutterInventoryTranslator.java @@ -65,6 +65,7 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl return rejectRequest(request); } CraftResultsDeprecatedStackRequestActionData craftData = (CraftResultsDeprecatedStackRequestActionData) data; + StonecutterContainer container = (StonecutterContainer) inventory; // Get the ID of the item we are cutting int id = inventory.getItem(0).getJavaId(); @@ -73,6 +74,7 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl if (results == null) { return rejectRequest(request); } + ItemStack javaOutput = ItemTranslator.translateToJava(craftData.getResultItems()[0]); int button = results.indexOf(javaOutput.getId()); // If we've already pressed the button with this item, no need to press it again! @@ -86,6 +88,7 @@ public class StonecutterInventoryTranslator extends AbstractBlockInventoryTransl inventory.setItem(1, GeyserItemStack.from(javaOutput), session); } } + return translateRequest(session, inventory, request); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java index ee8f03306..bf78d52c6 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -163,10 +163,11 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator stonecutterRecipeMap = new Int2ObjectOpenHashMap<>(); for (Int2ObjectMap.Entry> data : unsortedStonecutterData.int2ObjectEntrySet()) { + // Sort the list by each output item's Java identifier - this is how it's sorted on Java, and therefore + // We can get the correct order for button pressing data.getValue().sort(Comparator.comparing((stoneCuttingRecipeData -> - // Sort the list by each output item's Java identifier - this is how it's sorted on Java, and therefore - // We can get the correct order for button pressing ItemRegistry.getItem(stoneCuttingRecipeData.getResult()).getJavaIdentifier()))); + // Now that it's sorted, let's translate these recipes for (StoneCuttingRecipeData stoneCuttingData : data.getValue()) { // As of 1.16.4, all stonecutter recipes have one ingredient option @@ -174,9 +175,11 @@ public class JavaDeclareRecipesTranslator extends PacketTranslator } } - if (Arrays.equals(ingredients, mirroredIngredients)) { - continue; - } else if (!testShapedRecipe(mirroredIngredients, inventory, gridDimensions, firstRow, height, firstCol, width)) { + if (Arrays.equals(ingredients, mirroredIngredients) || + !testShapedRecipe(mirroredIngredients, inventory, gridDimensions, firstRow, height, firstCol, width)) { continue; } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java index ac194eca7..1432a06e1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java @@ -77,6 +77,7 @@ public class JavaTradeListTranslator extends PacketTranslator tags = new ArrayList<>(addExtraTrade ? packet.getTrades().length + 1 : packet.getTrades().length); @@ -119,6 +120,7 @@ public class JavaTradeListTranslator extends PacketTranslator expTags = new ArrayList<>(5); expTags.add(NbtMap.builder().putInt("0", 0).build()); expTags.add(NbtMap.builder().putInt("1", 10).build()); @@ -126,6 +128,7 @@ public class JavaTradeListTranslator extends PacketTranslator Date: Mon, 8 Mar 2021 16:59:59 -0500 Subject: [PATCH 133/161] Add spacing in lectern Spigot get code --- .../spigot/world/manager/GeyserSpigotWorldManager.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java index a35dd5fc6..13f696fd5 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -187,11 +187,13 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { if ((bukkitPlayer = Bukkit.getPlayer(session.getPlayerEntity().getUsername())) == null) { return; } + Block block = bukkitPlayer.getWorld().getBlockAt(x, y, z); if (!(block.getState() instanceof Lectern)) { session.getConnector().getLogger().error("Lectern expected at: " + Vector3i.from(x, y, z).toString() + " but was not! " + block.toString()); return; } + Lectern lectern = (Lectern) block.getState(); ItemStack itemStack = lectern.getInventory().getItem(0); if (itemStack == null || !(itemStack.getItemMeta() instanceof BookMeta)) { @@ -202,6 +204,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { // We don't care; return return; } + BookMeta bookMeta = (BookMeta) itemStack.getItemMeta(); // On the count: allow the book to show/open even there are no pages. We know there is a book here, after all, and this matches Java behavior boolean hasBookPages = bookMeta.getPageCount() > 0; @@ -226,6 +229,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { .putString("text", ""); pages.add(pageBuilder.build()); } + bookTag.putCompound("tag", NbtMap.builder().putList("pages", NbtType.COMPOUND, pages).build()); lecternTag.putCompound("book", bookTag.build()); NbtMap blockEntityTag = lecternTag.build(); From efc7e43e02c5f6882fc6d8659ad8e1f8eb46c657 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 8 Mar 2021 22:00:44 +0000 Subject: [PATCH 134/161] Fix settings not displaying due to bedrock bug by delaying 1s (#2010) * Fix settings not displaying due to bedrock bug by delaying 1s * Update BedrockServerSettingsRequestTranslator.java --- .../BedrockServerSettingsRequestTranslator.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java index 997bba8aa..3f3226280 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockServerSettingsRequestTranslator.java @@ -32,6 +32,8 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.SettingsUtils; +import java.util.concurrent.TimeUnit; + @Translator(packet = ServerSettingsRequestPacket.class) public class BedrockServerSettingsRequestTranslator extends PacketTranslator { @@ -39,9 +41,12 @@ public class BedrockServerSettingsRequestTranslator extends PacketTranslator { + ServerSettingsResponsePacket serverSettingsResponsePacket = new ServerSettingsResponsePacket(); + serverSettingsResponsePacket.setFormData(session.getSettingsForm().getJSONData()); + serverSettingsResponsePacket.setFormId(SettingsUtils.SETTINGS_FORM_ID); + session.sendUpstreamPacket(serverSettingsResponsePacket); + }, 1, TimeUnit.SECONDS); } } From ffae69471d39e28abf9a2c2960397bf76abdd68e Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 8 Mar 2021 18:13:26 -0500 Subject: [PATCH 135/161] Add protection for out-of-bounds inventory management --- .../java/org/geysermc/connector/inventory/Inventory.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java index 9246c0478..11a0034ad 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -30,6 +30,7 @@ import com.nukkitx.math.vector.Vector3i; import lombok.Getter; import lombok.NonNull; import lombok.Setter; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import java.util.Arrays; @@ -86,10 +87,18 @@ public class Inventory { } public GeyserItemStack getItem(int slot) { + if (slot > this.size) { + GeyserConnector.getInstance().getLogger().debug("Tried to get an item out of bounds! " + this.toString()); + return GeyserItemStack.EMPTY; + } return items[slot]; } public void setItem(int slot, @NonNull GeyserItemStack newItem, GeyserSession session) { + if (slot > this.size) { + session.getConnector().getLogger().debug("Tried to set an item out of bounds! " + this.toString()); + return; + } GeyserItemStack oldItem = items[slot]; updateItemNetId(oldItem, newItem, session); items[slot] = newItem; From d80372e0b42c8cb72564979c98e91f6329b13306 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 8 Mar 2021 18:17:14 -0500 Subject: [PATCH 136/161] Also check out-of-bounds on player inventory --- .../geysermc/connector/inventory/PlayerInventory.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java index 4b81853aa..76b2e5fbe 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java @@ -28,6 +28,7 @@ package org.geysermc.connector.inventory; import lombok.Getter; import lombok.NonNull; import lombok.Setter; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; public class PlayerInventory extends Inventory { @@ -55,10 +56,18 @@ public class PlayerInventory extends Inventory { } public GeyserItemStack getItemInHand() { + if (36 + heldItemSlot > this.size) { + GeyserConnector.getInstance().getLogger().debug("Held item slot was larger than expected!"); + return GeyserItemStack.EMPTY; + } return items[36 + heldItemSlot]; } public void setItemInHand(@NonNull GeyserItemStack item) { + if (36 + heldItemSlot > this.size) { + GeyserConnector.getInstance().getLogger().debug("Held item slot was larger than expected!"); + return; + } items[36 + heldItemSlot] = item; } From 9ae7c1de252102967a13d9a445dc2fcc151c33d8 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Tue, 9 Mar 2021 12:51:48 -0500 Subject: [PATCH 137/161] 1.16.210 support (#2019) This commit implements 1.16.210 support while still keeping 1.16.100 and 1.16.210 compatibility. Co-authored-by: AJ Ferguson Co-authored-by: rtm516 --- .../world/GeyserSpigotBlockPlaceListener.java | 4 +- connector/pom.xml | 2 +- .../entity/CommandBlockMinecartEntity.java | 5 +- .../entity/DefaultBlockMinecartEntity.java | 16 +- .../connector/entity/FallingBlockEntity.java | 9 +- .../entity/FurnaceMinecartEntity.java | 6 +- .../connector/entity/ItemFrameEntity.java | 31 ++- .../connector/entity/MinecartEntity.java | 3 +- .../entity/SpawnerMinecartEntity.java | 5 +- .../entity/living/monster/EndermanEntity.java | 3 +- .../connector/network/BedrockProtocol.java | 8 +- .../network/UpstreamPacketHandler.java | 9 +- .../network/session/GeyserSession.java | 7 + ...BedrockInventoryTransactionTranslator.java | 10 +- .../player/BedrockActionTranslator.java | 2 +- .../inventory/BlockInventoryTranslator.java | 3 +- .../DoubleChestInventoryTranslator.java | 14 +- .../SingleChestInventoryTranslator.java | 2 +- .../holder/BlockInventoryHolder.java | 7 +- .../translators/item/ItemTranslator.java | 10 +- .../java/world/JavaBlockChangeTranslator.java | 2 +- .../java/world/JavaChunkDataTranslator.java | 4 +- .../world/JavaPlayBuiltinSoundTranslator.java | 2 +- .../java/world/JavaPlayEffectTranslator.java | 4 +- .../world/JavaSpawnParticleTranslator.java | 19 +- .../block/GrassPathInteractionHandler.java | 2 +- .../sound/block/HoeInteractionHandler.java | 2 +- .../world/block/BlockStateValues.java | 12 - .../world/block/BlockTranslator.java | 262 +++++++++++------- .../world/block/BlockTranslator1_16_100.java | 59 ++++ .../world/block/BlockTranslator1_16_210.java | 43 +++ .../block/entity/BedrockOnlyBlockEntity.java | 4 +- .../FlowerPotBlockEntityTranslator.java | 12 +- .../translators/world/chunk/BlockStorage.java | 9 +- .../translators/world/chunk/ChunkSection.java | 4 +- .../world/chunk/EmptyChunkProvider.java | 58 ++++ .../geysermc/connector/utils/BlockUtils.java | 2 +- .../geysermc/connector/utils/ChunkUtils.java | 50 +--- .../bedrock/blockpalette.1_16_100.nbt | Bin 0 -> 104176 bytes .../bedrock/blockpalette.1_16_210.nbt | Bin 0 -> 34796 bytes .../main/resources/bedrock/blockpalette.nbt | Bin 1160789 -> 0 bytes connector/src/main/resources/mappings | 2 +- 42 files changed, 454 insertions(+), 254 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/EmptyChunkProvider.java create mode 100644 connector/src/main/resources/bedrock/blockpalette.1_16_100.nbt create mode 100644 connector/src/main/resources/bedrock/blockpalette.1_16_210.nbt delete mode 100644 connector/src/main/resources/bedrock/blockpalette.nbt diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java index 56fa7581b..51e68a263 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/GeyserSpigotBlockPlaceListener.java @@ -53,11 +53,11 @@ public class GeyserSpigotBlockPlaceListener implements Listener { placeBlockSoundPacket.setPosition(Vector3f.from(event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ())); placeBlockSoundPacket.setBabySound(false); if (worldManager.isLegacy()) { - placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(worldManager.getBlockAt(session, + placeBlockSoundPacket.setExtraData(session.getBlockTranslator().getBedrockBlockId(worldManager.getBlockAt(session, event.getBlockPlaced().getX(), event.getBlockPlaced().getY(), event.getBlockPlaced().getZ()))); } else { String javaBlockId = event.getBlockPlaced().getBlockData().getAsString(); - placeBlockSoundPacket.setExtraData(BlockTranslator.getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, BlockTranslator.JAVA_AIR_ID))); + placeBlockSoundPacket.setExtraData(session.getBlockTranslator().getBedrockBlockId(BlockTranslator.getJavaIdBlockMap().getOrDefault(javaBlockId, BlockTranslator.JAVA_AIR_ID))); } placeBlockSoundPacket.setIdentifier(":"); session.sendUpstreamPacket(placeBlockSoundPacket); diff --git a/connector/pom.xml b/connector/pom.xml index 1ee651a2a..fb253117c 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -31,7 +31,7 @@ com.github.CloudburstMC.Protocol - bedrock-v422 + bedrock-v428 42da92f compile diff --git a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java index 6ae65643c..52183c431 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/CommandBlockMinecartEntity.java @@ -31,7 +31,6 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import net.kyori.adventure.text.Component; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.chat.MessageTranslator; public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { @@ -60,8 +59,8 @@ public class CommandBlockMinecartEntity extends DefaultBlockMinecartEntity { * By default, the command block shown is purple on Bedrock, which does not match Java Edition's orange. */ @Override - public void updateDefaultBlockMetadata() { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.BEDROCK_RUNTIME_COMMAND_BLOCK_ID); + public void updateDefaultBlockMetadata(GeyserSession session) { + metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockRuntimeCommandBlockId()); metadata.put(EntityData.DISPLAY_OFFSET, 6); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java index 8ab368e70..805105c64 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/DefaultBlockMinecartEntity.java @@ -30,7 +30,6 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; /** * This class is used as a base for minecarts with a default block to display like furnaces and spawners @@ -44,10 +43,15 @@ public class DefaultBlockMinecartEntity extends MinecartEntity { public DefaultBlockMinecartEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, entityType, position, motion, rotation); - updateDefaultBlockMetadata(); metadata.put(EntityData.CUSTOM_DISPLAY, (byte) 1); } + @Override + public void spawnEntity(GeyserSession session) { + updateDefaultBlockMetadata(session); + super.spawnEntity(session); + } + @Override public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { @@ -56,7 +60,7 @@ public class DefaultBlockMinecartEntity extends MinecartEntity { customBlock = (int) entityMetadata.getValue(); if (showCustomBlock) { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(customBlock)); + metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(customBlock)); } } @@ -73,16 +77,16 @@ public class DefaultBlockMinecartEntity extends MinecartEntity { if (entityMetadata.getId() == 12) { if ((boolean) entityMetadata.getValue()) { showCustomBlock = true; - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(customBlock)); + metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(customBlock)); metadata.put(EntityData.DISPLAY_OFFSET, customBlockOffset); } else { showCustomBlock = false; - updateDefaultBlockMetadata(); + updateDefaultBlockMetadata(session); } } super.updateBedrockMetadata(entityMetadata, session); } - public void updateDefaultBlockMetadata() { } + public void updateDefaultBlockMetadata(GeyserSession session) { } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java index 76ca0567e..bd0fe9b80 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FallingBlockEntity.java @@ -31,14 +31,19 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityData; import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class FallingBlockEntity extends Entity { + private final int javaId; public FallingBlockEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, int javaId) { super(entityId, geyserId, entityType, position, motion, rotation); + this.javaId = javaId; + } - this.metadata.put(EntityData.VARIANT, BlockTranslator.getBedrockBlockId(javaId)); + @Override + public void spawnEntity(GeyserSession session) { + this.metadata.put(EntityData.VARIANT, session.getBlockTranslator().getBedrockBlockId(javaId)); + super.spawnEntity(session); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java index e3af51be6..fdf24f176 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FurnaceMinecartEntity.java @@ -44,15 +44,15 @@ public class FurnaceMinecartEntity extends DefaultBlockMinecartEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { if (entityMetadata.getId() == 13 && !showCustomBlock) { hasFuel = (boolean) entityMetadata.getValue(); - updateDefaultBlockMetadata(); + updateDefaultBlockMetadata(session); } super.updateBedrockMetadata(entityMetadata, session); } @Override - public void updateDefaultBlockMetadata() { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(hasFuel ? BlockTranslator.JAVA_RUNTIME_FURNACE_LIT_ID : BlockTranslator.JAVA_RUNTIME_FURNACE_ID)); + public void updateDefaultBlockMetadata(GeyserSession session) { + metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(hasFuel ? BlockTranslator.JAVA_RUNTIME_FURNACE_LIT_ID : BlockTranslator.JAVA_RUNTIME_FURNACE_ID)); metadata.put(EntityData.DISPLAY_OFFSET, 6); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java index 4f0a224e2..a898ea389 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/ItemFrameEntity.java @@ -40,7 +40,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.item.ItemEntry; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.item.ItemTranslator; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import java.util.concurrent.TimeUnit; @@ -49,15 +48,19 @@ import java.util.concurrent.TimeUnit; */ public class ItemFrameEntity extends Entity { + /** + * Used to construct the block entity tag on spawning. + */ + private final HangingDirection direction; /** * Used for getting the Bedrock block position. * Blocks deal with integers whereas entities deal with floats. */ - private final Vector3i bedrockPosition; + private Vector3i bedrockPosition; /** * Specific block 'state' we are emulating in Bedrock. */ - private final int bedrockRuntimeId; + private int bedrockRuntimeId; /** * Rotation of item in frame. */ @@ -69,19 +72,21 @@ public class ItemFrameEntity extends Entity { public ItemFrameEntity(long entityId, long geyserId, EntityType entityType, Vector3f position, Vector3f motion, Vector3f rotation, HangingDirection direction) { super(entityId, geyserId, entityType, position, motion, rotation); - NbtMapBuilder blockBuilder = NbtMap.builder() - .putString("name", "minecraft:frame") - .putInt("version", BlockTranslator.getBlockStateVersion()); - blockBuilder.put("states", NbtMap.builder() - .putInt("facing_direction", direction.ordinal()) - .putByte("item_frame_map_bit", (byte) 0) - .build()); - bedrockRuntimeId = BlockTranslator.getItemFrame(blockBuilder.build()); - bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ()); + this.direction = direction; } @Override public void spawnEntity(GeyserSession session) { + NbtMapBuilder blockBuilder = NbtMap.builder() + .putString("name", "minecraft:frame") + .putInt("version", session.getBlockTranslator().getBlockStateVersion()); + blockBuilder.put("states", NbtMap.builder() + .putInt("facing_direction", direction.ordinal()) + .putByte("item_frame_map_bit", (byte) 0) + .build()); + bedrockRuntimeId = session.getBlockTranslator().getItemFrame(blockBuilder.build()); + bedrockPosition = Vector3i.from(position.getFloorX(), position.getFloorY(), position.getFloorZ()); + session.getItemFrameCache().put(bedrockPosition, entityId); // Delay is required, or else loading in frames on chunk load is sketchy at best session.getConnector().getGeneralThreadPool().schedule(() -> { @@ -136,7 +141,7 @@ public class ItemFrameEntity extends Entity { UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); updateBlockPacket.setBlockPosition(bedrockPosition); - updateBlockPacket.setRuntimeId(BlockTranslator.BEDROCK_AIR_ID); + updateBlockPacket.setRuntimeId(session.getBlockTranslator().getBedrockAirId()); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); diff --git a/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java index ed5f28d17..49b12a3e1 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/MinecartEntity.java @@ -30,7 +30,6 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class MinecartEntity extends Entity { @@ -58,7 +57,7 @@ public class MinecartEntity extends Entity { if (!(this instanceof DefaultBlockMinecartEntity)) { // Handled in the DefaultBlockMinecartEntity class // Custom block if (entityMetadata.getId() == 10) { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue())); + metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId((int) entityMetadata.getValue())); } // Custom block offset diff --git a/connector/src/main/java/org/geysermc/connector/entity/SpawnerMinecartEntity.java b/connector/src/main/java/org/geysermc/connector/entity/SpawnerMinecartEntity.java index 143e36373..2f7af73eb 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/SpawnerMinecartEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/SpawnerMinecartEntity.java @@ -28,6 +28,7 @@ package org.geysermc.connector.entity; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.entity.EntityData; import org.geysermc.connector.entity.type.EntityType; +import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class SpawnerMinecartEntity extends DefaultBlockMinecartEntity { @@ -37,8 +38,8 @@ public class SpawnerMinecartEntity extends DefaultBlockMinecartEntity { } @Override - public void updateDefaultBlockMetadata() { - metadata.put(EntityData.DISPLAY_ITEM, BlockTranslator.getBedrockBlockId(BlockTranslator.JAVA_RUNTIME_SPAWNER_ID)); + public void updateDefaultBlockMetadata(GeyserSession session) { + metadata.put(EntityData.DISPLAY_ITEM, session.getBlockTranslator().getBedrockBlockId(BlockTranslator.JAVA_RUNTIME_SPAWNER_ID)); metadata.put(EntityData.DISPLAY_OFFSET, 6); } } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java index 3151ae474..0d265b56e 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/monster/EndermanEntity.java @@ -33,7 +33,6 @@ import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.LevelSoundEvent2Packet; import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; public class EndermanEntity extends MonsterEntity { @@ -45,7 +44,7 @@ public class EndermanEntity extends MonsterEntity { public void updateBedrockMetadata(EntityMetadata entityMetadata, GeyserSession session) { // Held block if (entityMetadata.getId() == 15) { - metadata.put(EntityData.CARRIED_BLOCK, BlockTranslator.getBedrockBlockId((int) entityMetadata.getValue())); + metadata.put(EntityData.CARRIED_BLOCK, session.getBlockTranslator().getBedrockBlockId((int) entityMetadata.getValue())); } // "Is screaming" - controls sound if (entityMetadata.getId() == 16) { diff --git a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java index d24cea328..3b5af7f99 100644 --- a/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java +++ b/connector/src/main/java/org/geysermc/connector/network/BedrockProtocol.java @@ -28,6 +28,7 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.v419.Bedrock_v419; import com.nukkitx.protocol.bedrock.v422.Bedrock_v422; +import com.nukkitx.protocol.bedrock.v428.Bedrock_v428; import java.util.ArrayList; import java.util.List; @@ -40,9 +41,7 @@ public class BedrockProtocol { * Default Bedrock codec that should act as a fallback. Should represent the latest available * release of the game that Geyser supports. */ - public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v422.V422_CODEC.toBuilder() - .minecraftVersion("1.16.201") - .build(); + public static final BedrockPacketCodec DEFAULT_BEDROCK_CODEC = Bedrock_v428.V428_CODEC; /** * A list of all supported Bedrock versions that can join Geyser */ @@ -52,9 +51,10 @@ public class BedrockProtocol { SUPPORTED_BEDROCK_CODECS.add(Bedrock_v419.V419_CODEC.toBuilder() .minecraftVersion("1.16.100/1.16.101") // We change this as 1.16.100.60 is a beta .build()); - SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder() + SUPPORTED_BEDROCK_CODECS.add(Bedrock_v422.V422_CODEC.toBuilder() .minecraftVersion("1.16.200/1.16.201") .build()); + SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC); } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index 7ebfaeda5..829ae23ef 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -26,15 +26,18 @@ package org.geysermc.connector.network; import com.nukkitx.protocol.bedrock.BedrockPacket; -import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; +import com.nukkitx.protocol.bedrock.data.ResourcePackType; import com.nukkitx.protocol.bedrock.packet.*; +import com.nukkitx.protocol.bedrock.v428.Bedrock_v428; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.common.AuthType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.session.cache.AdvancementsCache; import org.geysermc.connector.network.translators.PacketTranslatorRegistry; +import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_100; +import org.geysermc.connector.network.translators.world.block.BlockTranslator1_16_210; import org.geysermc.connector.utils.*; import java.io.FileInputStream; @@ -68,6 +71,10 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { session.getUpstream().getSession().setPacketCodec(packetCodec); + // Set the block translation based off of version + session.setBlockTranslator(packetCodec.getProtocolVersion() >= Bedrock_v428.V428_CODEC.getProtocolVersion() + ? BlockTranslator1_16_210.INSTANCE : BlockTranslator1_16_100.INSTANCE); + LoginEncryptionUtils.encryptPlayerConnection(connector, session, loginPacket); PlayStatusPacket playStatus = new PlayStatusPacket(); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index 885a910c1..adba703a7 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -88,6 +88,7 @@ import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.network.translators.collision.CollisionManager; import org.geysermc.connector.network.translators.inventory.EnchantmentInventoryTranslator; import org.geysermc.connector.network.translators.item.ItemRegistry; +import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.skin.SkinManager; import org.geysermc.connector.utils.*; import org.geysermc.floodgate.util.BedrockData; @@ -138,6 +139,12 @@ public class GeyserSession implements CommandSender { */ private final CollisionManager collisionManager; + /** + * Stores the block translations for this specific version. + */ + @Setter + private BlockTranslator blockTranslator; + private final Map skullCache = new ConcurrentHashMap<>(); private final Long2ObjectMap storedMaps = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 8263507b2..9b8b5b668 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -108,7 +108,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator= 2 && session.getGameMode() == GameMode.CREATIVE) { // Otherwise insufficient permissions - int blockState = BlockTranslator.getJavaBlockState(packet.getBlockRuntimeId()); + int blockState = session.getBlockTranslator().getJavaBlockState(packet.getBlockRuntimeId()); String blockName = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(blockState, ""); // In the future this can be used for structure blocks too, however not all elements // are available in each GUI @@ -253,7 +253,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator 0) { canModifyBedrock = new String[canModifyJava.size()]; for (int i = 0; i < canModifyBedrock.length; i++) { @@ -185,7 +185,7 @@ public abstract class ItemTranslator { if (!block.startsWith("minecraft:")) block = "minecraft:" + block; // Get the Bedrock identifier of the item and replace it. // This will unfortunately be limited - for example, beds and banners will be translated weirdly - canModifyBedrock[i] = BlockTranslator.getBedrockBlockIdentifier(block).replace("minecraft:", ""); + canModifyBedrock[i] = session.getBlockTranslator().getBedrockBlockIdentifier(block).replace("minecraft:", ""); } } return canModifyBedrock; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java index d74165b14..371446b7e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockChangeTranslator.java @@ -84,7 +84,7 @@ public class JavaBlockChangeTranslator extends PacketTranslator DOUBLE_CHEST_VALUES = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap FLOWER_POT_VALUES = new Int2ObjectOpenHashMap<>(); - private static final Map FLOWER_POT_BLOCKS = new HashMap<>(); private static final Int2IntMap NOTEBLOCK_PITCHES = new Int2IntOpenHashMap(); private static final Int2BooleanMap IS_STICKY_PISTON = new Int2BooleanOpenHashMap(); private static final Int2BooleanMap PISTON_VALUES = new Int2BooleanOpenHashMap(); @@ -196,15 +193,6 @@ public class BlockStateValues { return FLOWER_POT_VALUES; } - /** - * Get the map of contained flower pot plants to Bedrock CompoundTag - * - * @return Map of flower pot blocks. - */ - public static Map getFlowerPotBlocks() { - return FLOWER_POT_BLOCKS; - } - /** * The note that noteblocks output when hit is part of the block state in Java but sent as a BlockEventPacket in Bedrock. * This gives an integer pitch that Bedrock can use. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index 1d7c86a53..64bbae210 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -34,16 +34,20 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import lombok.Getter; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.translators.world.chunk.ChunkSection; +import org.geysermc.connector.network.translators.world.chunk.EmptyChunkProvider; import org.geysermc.connector.utils.FileUtils; -import org.reflections.Reflections; import java.io.DataInputStream; import java.io.InputStream; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.zip.GZIPInputStream; -public class BlockTranslator { +public abstract class BlockTranslator { /** * The Java block runtime ID of air */ @@ -51,11 +55,11 @@ public class BlockTranslator { /** * The Bedrock block runtime ID of air */ - public static final int BEDROCK_AIR_ID; - public static final int BEDROCK_WATER_ID; + private final int bedrockAirId; + private final int bedrockWaterId; - private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap(); - private static final Int2IntMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2IntOpenHashMap(); + private final Int2IntMap javaToBedrockBlockMap = new Int2IntOpenHashMap(); + private final Int2IntMap bedrockToJavaBlockMap = new Int2IntOpenHashMap(); /** * Stores a list of differences in block identifiers. * Items will not be added to this list if the key and value is the same. @@ -63,7 +67,8 @@ public class BlockTranslator { private static final Object2ObjectMap JAVA_TO_BEDROCK_IDENTIFIERS = new Object2ObjectOpenHashMap<>(); private static final BiMap JAVA_ID_BLOCK_MAP = HashBiMap.create(); private static final IntSet WATERLOGGED = new IntOpenHashSet(); - private static final Object2IntMap ITEM_FRAMES = new Object2IntOpenHashMap<>(); + private final Object2IntMap itemFrames = new Object2IntOpenHashMap<>(); + private final Map flowerPotBlocks = new HashMap<>(); // Bedrock carpet ID, used in LlamaEntity.java for decoration public static final int CARPET = 171; @@ -85,7 +90,10 @@ public class BlockTranslator { /** * Runtime command block ID, used for fixing command block minecart appearances */ - public static final int BEDROCK_RUNTIME_COMMAND_BLOCK_ID; + @Getter + private final int bedrockRuntimeCommandBlockId; + + private final EmptyChunkProvider emptyChunkProvider; /** * A list of all Java runtime wool IDs, for use with block breaking math and shears @@ -98,63 +106,30 @@ public class BlockTranslator { public static final int JAVA_RUNTIME_SPAWNER_ID; - private static final int BLOCK_STATE_VERSION = 17825808; + /** + * Stores the raw blocks JSON until it is no longer needed. + */ + public static JsonNode BLOCKS_JSON; static { - /* Load block palette */ - InputStream stream = FileUtils.getResource("bedrock/blockpalette.nbt"); - - NbtList blocksTag; - try (NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(stream))) { - NbtMap blockPalette = (NbtMap) nbtInputStream.readTag(); - blocksTag = (NbtList) blockPalette.getList("blocks", NbtType.COMPOUND); - } catch (Exception e) { - throw new AssertionError("Unable to get blocks from runtime block states", e); - } - - // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, - // as we no longer send a block palette - Object2IntMap blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size()); - - for (int i = 0; i < blocksTag.size(); i++) { - NbtMap tag = blocksTag.get(i); - NbtMap blockTag = tag.getCompound("block"); - if (blockStateOrderedMap.containsKey(blockTag)) { - throw new AssertionError("Duplicate block states in Bedrock palette"); - } - blockStateOrderedMap.put(blockTag, i); - } - - stream = FileUtils.getResource("mappings/blocks.json"); - JsonNode blocks; + InputStream stream = FileUtils.getResource("mappings/blocks.json"); try { - blocks = GeyserConnector.JSON_MAPPER.readTree(stream); + BLOCKS_JSON = GeyserConnector.JSON_MAPPER.readTree(stream); } catch (Exception e) { throw new AssertionError("Unable to load Java block mappings", e); } - Reflections ref = GeyserConnector.getInstance().useXmlReflections() ? FileUtils.getReflections("org.geysermc.connector.network.translators.world.block.entity") - : new Reflections("org.geysermc.connector.network.translators.world.block.entity"); - - int waterRuntimeId = -1; int javaRuntimeId = -1; - int airRuntimeId = -1; int cobwebRuntimeId = -1; - int commandBlockRuntimeId = -1; int furnaceRuntimeId = -1; int furnaceLitRuntimeId = -1; int spawnerRuntimeId = -1; int uniqueJavaId = -1; - Iterator> blocksIterator = blocks.fields(); + Iterator> blocksIterator = BLOCKS_JSON.fields(); while (blocksIterator.hasNext()) { javaRuntimeId++; Map.Entry entry = blocksIterator.next(); String javaId = entry.getKey(); - NbtMap blockTag = buildBedrockState(entry.getValue()); - int bedrockRuntimeId = blockStateOrderedMap.getOrDefault(blockTag, -1); - if (bedrockRuntimeId == -1) { - throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID!"); - } // TODO fix this, (no block should have a null hardness) JsonNode hardnessNode = entry.getValue().get("block_hardness"); @@ -196,42 +171,17 @@ public class BlockTranslator { String bedrockIdentifier = entry.getValue().get("bedrock_identifier").asText(); + // Keeping this here since this is currently unchanged between versions if (!cleanJavaIdentifier.equals(bedrockIdentifier)) { JAVA_TO_BEDROCK_IDENTIFIERS.put(cleanJavaIdentifier, bedrockIdentifier); } - // Get the tag needed for non-empty flower pots - if (entry.getValue().get("pottable") != null) { - BlockStateValues.getFlowerPotBlocks().put(cleanJavaIdentifier, buildBedrockState(entry.getValue())); - } - - if ("minecraft:water[level=0]".equals(javaId)) { - waterRuntimeId = bedrockRuntimeId; - } - boolean waterlogged = entry.getKey().contains("waterlogged=true") - || javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass"); - - if (waterlogged) { - BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId | 1 << 31, javaRuntimeId); - WATERLOGGED.add(javaRuntimeId); - } else { - BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId, javaRuntimeId); - } - - JAVA_TO_BEDROCK_BLOCK_MAP.put(javaRuntimeId, bedrockRuntimeId); - - if (bedrockIdentifier.equals("minecraft:air")) { - airRuntimeId = bedrockRuntimeId; - - } else if (javaId.contains("wool")) { + if (javaId.contains("wool")) { JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); } else if (javaId.contains("cobweb")) { cobwebRuntimeId = javaRuntimeId; - } else if (javaId.equals("minecraft:command_block[conditional=false,facing=north]")) { - commandBlockRuntimeId = bedrockRuntimeId; - } else if (javaId.startsWith("minecraft:furnace[facing=north")) { if (javaId.contains("lit=true")) { furnaceLitRuntimeId = javaRuntimeId; @@ -249,11 +199,6 @@ public class BlockTranslator { } JAVA_RUNTIME_COBWEB_ID = cobwebRuntimeId; - if (commandBlockRuntimeId == -1) { - throw new AssertionError("Unable to find command block in palette"); - } - BEDROCK_RUNTIME_COMMAND_BLOCK_ID = commandBlockRuntimeId; - if (furnaceRuntimeId == -1) { throw new AssertionError("Unable to find furnace in palette"); } @@ -269,35 +214,117 @@ public class BlockTranslator { } JAVA_RUNTIME_SPAWNER_ID = spawnerRuntimeId; + BlockTranslator1_16_100.init(); + BlockTranslator1_16_210.init(); + BLOCKS_JSON = null; // We no longer require this so let it garbage collect away + } + + public BlockTranslator(String paletteFile) { + /* Load block palette */ + InputStream stream = FileUtils.getResource(paletteFile); + + NbtList blocksTag; + try (NBTInputStream nbtInputStream = new NBTInputStream(new DataInputStream(new GZIPInputStream(stream)))) { + NbtMap blockPalette = (NbtMap) nbtInputStream.readTag(); + blocksTag = (NbtList) blockPalette.getList("blocks", NbtType.COMPOUND); + } catch (Exception e) { + throw new AssertionError("Unable to get blocks from runtime block states", e); + } + + // New since 1.16.100 - find the block runtime ID by the order given to us in the block palette, + // as we no longer send a block palette + Object2IntMap blockStateOrderedMap = new Object2IntOpenHashMap<>(blocksTag.size()); + + for (int i = 0; i < blocksTag.size(); i++) { + NbtMap tag = blocksTag.get(i); + if (blockStateOrderedMap.containsKey(tag)) { + throw new AssertionError("Duplicate block states in Bedrock palette: " + tag); + } + blockStateOrderedMap.put(tag, i); + } + + int airRuntimeId = -1; + int commandBlockRuntimeId = -1; + int javaRuntimeId = -1; + int waterRuntimeId = -1; + Iterator> blocksIterator = BLOCKS_JSON.fields(); + while (blocksIterator.hasNext()) { + javaRuntimeId++; + Map.Entry entry = blocksIterator.next(); + String javaId = entry.getKey(); + + NbtMap blockTag = buildBedrockState(entry.getValue()); + int bedrockRuntimeId = blockStateOrderedMap.getOrDefault(blockTag, -1); + if (bedrockRuntimeId == -1) { + throw new RuntimeException("Unable to find " + javaId + " Bedrock runtime ID! Built compound tag: \n" + blockTag); + } + + switch (javaId) { + case "minecraft:air": + airRuntimeId = bedrockRuntimeId; + break; + case "minecraft:water[level=0]": + waterRuntimeId = bedrockRuntimeId; + break; + case "minecraft:command_block[conditional=false,facing=north]": + commandBlockRuntimeId = bedrockRuntimeId; + break; + } + + boolean waterlogged = entry.getKey().contains("waterlogged=true") + || javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass"); + + if (waterlogged) { + bedrockToJavaBlockMap.putIfAbsent(bedrockRuntimeId | 1 << 31, javaRuntimeId); + WATERLOGGED.add(javaRuntimeId); + } else { + bedrockToJavaBlockMap.putIfAbsent(bedrockRuntimeId, javaRuntimeId); + } + + String cleanJavaIdentifier = entry.getKey().split("\\[")[0]; + + // Get the tag needed for non-empty flower pots + if (entry.getValue().get("pottable") != null) { + flowerPotBlocks.put(cleanJavaIdentifier, buildBedrockState(entry.getValue())); + } + + javaToBedrockBlockMap.put(javaRuntimeId, bedrockRuntimeId); + } + + if (commandBlockRuntimeId == -1) { + throw new AssertionError("Unable to find command block in palette"); + } + bedrockRuntimeCommandBlockId = commandBlockRuntimeId; + if (waterRuntimeId == -1) { throw new AssertionError("Unable to find water in palette"); } - BEDROCK_WATER_ID = waterRuntimeId; + bedrockWaterId = waterRuntimeId; if (airRuntimeId == -1) { throw new AssertionError("Unable to find air in palette"); } - BEDROCK_AIR_ID = airRuntimeId; + bedrockAirId = airRuntimeId; // Loop around again to find all item frame runtime IDs for (Object2IntMap.Entry entry : blockStateOrderedMap.object2IntEntrySet()) { if (entry.getKey().getString("name").equals("minecraft:frame")) { - ITEM_FRAMES.put(entry.getKey(), entry.getIntValue()); + itemFrames.put(entry.getKey(), entry.getIntValue()); } } - } - private BlockTranslator() { + this.emptyChunkProvider = new EmptyChunkProvider(bedrockAirId); } public static void init() { // no-op } - private static NbtMap buildBedrockState(JsonNode node) { + private NbtMap buildBedrockState(JsonNode node) { NbtMapBuilder tagBuilder = NbtMap.builder(); - tagBuilder.putString("name", node.get("bedrock_identifier").textValue()) - .putInt("version", BlockTranslator.BLOCK_STATE_VERSION); + String bedrockIdentifier = node.get("bedrock_identifier").textValue(); + tagBuilder.putString("name", bedrockIdentifier) + .putInt("version", getBlockStateVersion()); NbtMapBuilder statesBuilder = NbtMap.builder(); @@ -320,36 +347,67 @@ public class BlockTranslator { } } } - tagBuilder.put("states", statesBuilder.build()); + tagBuilder.put("states", adjustBlockStateForVersion(bedrockIdentifier, statesBuilder).build()); return tagBuilder.build(); } - public static int getBedrockBlockId(int state) { - return JAVA_TO_BEDROCK_BLOCK_MAP.get(state); + /** + * @return an adjusted state list, if necessary, that converts Geyser's new mapping to Bedrock's older version + * of the mapping. + */ + protected NbtMapBuilder adjustBlockStateForVersion(String bedrockIdentifier, NbtMapBuilder statesBuilder) { + return statesBuilder; } - public static int getJavaBlockState(int bedrockId) { - return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId); + public int getBedrockBlockId(int state) { + return javaToBedrockBlockMap.get(state); + } + + public int getJavaBlockState(int bedrockId) { + return bedrockToJavaBlockMap.get(bedrockId); } /** * @param javaIdentifier the Java identifier of the block to search for * @return the Bedrock identifier if different, or else the Java identifier */ - public static String getBedrockBlockIdentifier(String javaIdentifier) { + public String getBedrockBlockIdentifier(String javaIdentifier) { return JAVA_TO_BEDROCK_IDENTIFIERS.getOrDefault(javaIdentifier, javaIdentifier); } - public static int getItemFrame(NbtMap tag) { - return ITEM_FRAMES.getOrDefault(tag, -1); + public int getItemFrame(NbtMap tag) { + return itemFrames.getOrDefault(tag, -1); } - public static boolean isItemFrame(int bedrockBlockRuntimeId) { - return ITEM_FRAMES.values().contains(bedrockBlockRuntimeId); + public boolean isItemFrame(int bedrockBlockRuntimeId) { + return itemFrames.values().contains(bedrockBlockRuntimeId); } - public static int getBlockStateVersion() { - return BLOCK_STATE_VERSION; + /** + * Get the map of contained flower pot plants to Bedrock CompoundTag + * + * @return Map of flower pot blocks. + */ + public Map getFlowerPotBlocks() { + return flowerPotBlocks; + } + + public int getBedrockAirId() { + return bedrockAirId; + } + + public int getBedrockWaterId() { + return bedrockWaterId; + } + + public abstract int getBlockStateVersion(); + + public byte[] getEmptyChunkData() { + return emptyChunkProvider.getEmptyLevelChunkData(); + } + + public ChunkSection getEmptyChunkSection() { + return emptyChunkProvider.getEmptySection(); } /** @@ -368,10 +426,6 @@ public class BlockTranslator { return JAVA_ID_BLOCK_MAP; } - public static int getJavaWaterloggedState(int bedrockId) { - return BEDROCK_TO_JAVA_BLOCK_MAP.get(1 << 31 | bedrockId); - } - /** * Get the item a Java client would receive when pressing * the Pick Block key on a specific Java block state. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java new file mode 100644 index 000000000..e10a503ea --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_100.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.world.block; + +import com.google.common.collect.ImmutableSet; +import com.nukkitx.nbt.NbtMapBuilder; + +import java.util.Set; + +public class BlockTranslator1_16_100 extends BlockTranslator { + private static final Set CORRECTED_STATES = ImmutableSet.of("minecraft:stripped_warped_stem", + "minecraft:stripped_warped_hyphae", "minecraft:stripped_crimson_stem", "minecraft:stripped_crimson_hyphae"); + + public static final BlockTranslator1_16_100 INSTANCE = new BlockTranslator1_16_100(); + + public BlockTranslator1_16_100() { + super("bedrock/blockpalette.1_16_100.nbt"); + } + + @Override + public int getBlockStateVersion() { + return 17825808; + } + + @Override + protected NbtMapBuilder adjustBlockStateForVersion(String bedrockIdentifier, NbtMapBuilder statesBuilder) { + if (CORRECTED_STATES.contains(bedrockIdentifier)) { + statesBuilder.putInt("deprecated", 0); + } + return super.adjustBlockStateForVersion(bedrockIdentifier, statesBuilder); + } + + public static void init() { + // no-op + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java new file mode 100644 index 000000000..58861cb9c --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator1_16_210.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.world.block; + +public class BlockTranslator1_16_210 extends BlockTranslator { + public static final BlockTranslator1_16_210 INSTANCE = new BlockTranslator1_16_210(); + + public BlockTranslator1_16_210() { + super("bedrock/blockpalette.1_16_210.nbt"); + } + + @Override + public int getBlockStateVersion() { + return 17879555; + } + + public static void init() { + // no-op + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java index e05fcc67b..2417f1e6e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/BedrockOnlyBlockEntity.java @@ -47,9 +47,9 @@ public interface BedrockOnlyBlockEntity { * @param blockState Java BlockState of block. * @return Bedrock tag, or null if not a Bedrock-only Block Entity */ - static NbtMap getTag(Vector3i position, int blockState) { + static NbtMap getTag(GeyserSession session, Vector3i position, int blockState) { if (FlowerPotBlockEntityTranslator.isFlowerBlock(blockState)) { - return FlowerPotBlockEntityTranslator.getTag(blockState, position); + return FlowerPotBlockEntityTranslator.getTag(session, blockState, position); } else if (PistonBlockEntityTranslator.isBlock(blockState)) { return PistonBlockEntityTranslator.getTag(blockState, position); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java index 9eebe37d7..062fd4922 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/FlowerPotBlockEntityTranslator.java @@ -31,7 +31,6 @@ import com.nukkitx.nbt.NbtMapBuilder; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockStateValues; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockEntityUtils; public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, RequiresBlockState { @@ -50,7 +49,7 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R * @param position Bedrock position of flower pot. * @return Bedrock tag of flower pot. */ - public static NbtMap getTag(int blockState, Vector3i position) { + public static NbtMap getTag(GeyserSession session, int blockState, Vector3i position) { NbtMapBuilder tagBuilder = NbtMap.builder() .putInt("x", position.getX()) .putInt("y", position.getY()) @@ -62,7 +61,7 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R if (name != null) { // Get the Bedrock CompoundTag of the block. // This is where we need to store the *Java* name because Bedrock has six minecraft:sapling blocks with different block states. - NbtMap plant = BlockStateValues.getFlowerPotBlocks().get(name); + NbtMap plant = session.getBlockTranslator().getFlowerPotBlocks().get(name); if (plant != null) { tagBuilder.put("PlantBlock", plant.toBuilder().build()); } @@ -77,15 +76,16 @@ public class FlowerPotBlockEntityTranslator implements BedrockOnlyBlockEntity, R @Override public void updateBlock(GeyserSession session, int blockState, Vector3i position) { - BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + NbtMap tag = getTag(session, blockState, position); + BlockEntityUtils.updateBlockEntity(session, tag, position); UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); - updateBlockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(blockState)); + updateBlockPacket.setRuntimeId(session.getBlockTranslator().getBedrockBlockId(blockState)); updateBlockPacket.setBlockPosition(position); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NEIGHBORS); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.NETWORK); updateBlockPacket.getFlags().add(UpdateBlockPacket.Flag.PRIORITY); session.sendUpstreamPacket(updateBlockPacket); - BlockEntityUtils.updateBlockEntity(session, getTag(blockState, position), position); + BlockEntityUtils.updateBlockEntity(session, tag, position); } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java index 7a5086241..672fa1a35 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/BlockStorage.java @@ -30,7 +30,6 @@ import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; import lombok.Getter; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; @@ -44,14 +43,14 @@ public class BlockStorage { private final IntList palette; private BitArray bitArray; - public BlockStorage() { - this(BitArrayVersion.V2); + public BlockStorage(int airBlockId) { + this(airBlockId, BitArrayVersion.V2); } - public BlockStorage(BitArrayVersion version) { + public BlockStorage(int airBlockId, BitArrayVersion version) { this.bitArray = version.createArray(SIZE); this.palette = new IntArrayList(16); - this.palette.add(BlockTranslator.BEDROCK_AIR_ID); // Air is at the start of every palette and controls what the default block is in second-layer non-air block spaces. + this.palette.add(airBlockId); // Air is at the start of every palette and controls what the default block is in second-layer non-air block spaces. } public BlockStorage(BitArray bitArray, IntList palette) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java index 2709e3e23..53528d654 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/ChunkSection.java @@ -34,8 +34,8 @@ public class ChunkSection { private final BlockStorage[] storage; - public ChunkSection() { - this(new BlockStorage[]{new BlockStorage(), new BlockStorage()}); + public ChunkSection(int airBlockId) { + this(new BlockStorage[]{new BlockStorage(airBlockId), new BlockStorage(airBlockId)}); } public ChunkSection(BlockStorage[] storage) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/EmptyChunkProvider.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/EmptyChunkProvider.java new file mode 100644 index 000000000..1bc7d684f --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/chunk/EmptyChunkProvider.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.connector.network.translators.world.chunk; + +import com.nukkitx.nbt.NBTOutputStream; +import com.nukkitx.nbt.NbtMap; +import com.nukkitx.nbt.NbtUtils; +import lombok.Getter; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class EmptyChunkProvider { + @Getter + private final byte[] emptyLevelChunkData; + @Getter + private final ChunkSection emptySection; + + public EmptyChunkProvider(int airId) { + BlockStorage emptyStorage = new BlockStorage(airId); + emptySection = new ChunkSection(new BlockStorage[]{emptyStorage}); + + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size + + try (NBTOutputStream stream = NbtUtils.createNetworkWriter(outputStream)) { + stream.writeTag(NbtMap.EMPTY); + } + + emptyLevelChunkData = outputStream.toByteArray(); + } catch (IOException e) { + throw new AssertionError("Unable to generate empty level chunk data"); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java index c859b9f65..07fe9744d 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java @@ -132,7 +132,7 @@ public class BlockUtils { miningFatigueLevel = session.getEffectCache().getEffectLevel(Effect.SLOWER_DIG); boolean isInWater = session.getConnector().getConfig().isCacheChunks() - && BlockTranslator.getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt())) == BlockTranslator.BEDROCK_WATER_ID; + && session.getBlockTranslator().getBedrockBlockId(session.getConnector().getWorldManager().getBlockAt(session, session.getPlayerEntity().getPosition().toInt())) == session.getBlockTranslator().getBedrockWaterId(); boolean insideOfWaterWithoutAquaAffinity = isInWater && ItemUtils.getEnchantmentLevel(Optional.ofNullable(session.getInventory().getItem(5)).map(ItemStack::getNbt).orElse(null), "minecraft:aqua_affinity") < 1; diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index e5e3f36d7..65c15eb21 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -36,9 +36,7 @@ import com.github.steveice10.opennbt.tag.builtin.StringTag; import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.math.vector.Vector2i; import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.nbt.NBTOutputStream; import com.nukkitx.nbt.NbtMap; -import com.nukkitx.nbt.NbtUtils; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; @@ -64,13 +62,11 @@ import org.geysermc.connector.network.translators.world.chunk.ChunkSection; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArray; import org.geysermc.connector.network.translators.world.chunk.bitarray.BitArrayVersion; -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.util.ArrayList; import java.util.BitSet; import java.util.List; -import static org.geysermc.connector.network.translators.world.block.BlockTranslator.*; +import static org.geysermc.connector.network.translators.world.block.BlockTranslator.JAVA_AIR_ID; @UtilityClass public class ChunkUtils { @@ -80,26 +76,6 @@ public class ChunkUtils { */ public static final Object2IntMap CACHED_BLOCK_ENTITIES = new Object2IntOpenHashMap<>(); - private static final NbtMap EMPTY_TAG = NbtMap.builder().build(); - public static final byte[] EMPTY_LEVEL_CHUNK_DATA; - - public static final BlockStorage EMPTY_STORAGE = new BlockStorage(); - public static final ChunkSection EMPTY_SECTION = new ChunkSection(new BlockStorage[]{ EMPTY_STORAGE }); - - static { - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - outputStream.write(new byte[258]); // Biomes + Border Size + Extra Data Size - - try (NBTOutputStream stream = NbtUtils.createNetworkWriter(outputStream)) { - stream.writeTag(EMPTY_TAG); - } - - EMPTY_LEVEL_CHUNK_DATA = outputStream.toByteArray(); - } catch (IOException e) { - throw new AssertionError("Unable to generate empty level chunk data"); - } - } - private static int indexYZXtoXZY(int yzx) { return (yzx >> 8) | (yzx & 0x0F0) | ((yzx & 0x00F) << 8); } @@ -161,20 +137,20 @@ public class ChunkUtils { if (javaPalette instanceof GlobalPalette) { // As this is the global palette, simply iterate through the whole chunk section once - ChunkSection section = new ChunkSection(); + ChunkSection section = new ChunkSection(session.getBlockTranslator().getBedrockAirId()); for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { int javaId = javaData.get(yzx); - int bedrockId = BlockTranslator.getBedrockBlockId(javaId); + int bedrockId = session.getBlockTranslator().getBedrockBlockId(javaId); int xzy = indexYZXtoXZY(yzx); section.getBlockStorageArray()[0].setFullBlock(xzy, bedrockId); if (BlockTranslator.isWaterlogged(javaId)) { - section.getBlockStorageArray()[1].setFullBlock(xzy, BEDROCK_WATER_ID); + section.getBlockStorageArray()[1].setFullBlock(xzy, session.getBlockTranslator().getBedrockWaterId()); } // Check if block is piston or flower to see if we'll need to create additional block entities, as they're only block entities in Bedrock if (BlockStateValues.getFlowerPotValues().containsKey(javaId) || BlockStateValues.getPistonValues().containsKey(javaId)) { - bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag( + bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(session, Vector3i.from((column.getX() << 4) + (yzx & 0xF), (sectionY << 4) + ((yzx >> 8) & 0xF), (column.getZ() << 4) + ((yzx >> 4) & 0xF)), javaId )); @@ -191,7 +167,7 @@ public class ChunkUtils { // Iterate through palette and convert state IDs to Bedrock, doing some additional checks as we go for (int i = 0; i < javaPalette.size(); i++) { int javaId = javaPalette.idToState(i); - bedrockPalette.add(BlockTranslator.getBedrockBlockId(javaId)); + bedrockPalette.add(session.getBlockTranslator().getBedrockBlockId(javaId)); if (BlockTranslator.isWaterlogged(javaId)) { waterloggedPaletteIds.set(i); @@ -210,7 +186,7 @@ public class ChunkUtils { for (int yzx = 0; yzx < BlockStorage.SIZE; yzx++) { int paletteId = javaData.get(yzx); if (pistonOrFlowerPaletteIds.get(paletteId)) { - bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag( + bedrockOnlyBlockEntities.add(BedrockOnlyBlockEntity.getTag(session, Vector3i.from((column.getX() << 4) + (yzx & 0xF), (sectionY << 4) + ((yzx >> 8) & 0xF), (column.getZ() << 4) + ((yzx >> 4) & 0xF)), javaPalette.idToState(paletteId) )); @@ -247,8 +223,8 @@ public class ChunkUtils { // V1 palette IntList layer1Palette = new IntArrayList(2); - layer1Palette.add(BEDROCK_AIR_ID); // Air - see BlockStorage's constructor for more information - layer1Palette.add(BEDROCK_WATER_ID); + layer1Palette.add(session.getBlockTranslator().getBedrockAirId()); // Air - see BlockStorage's constructor for more information + layer1Palette.add(session.getBlockTranslator().getBedrockWaterId()); layers = new BlockStorage[]{ layer0, new BlockStorage(BitArrayVersion.V1.createArray(BlockStorage.SIZE, layer1Data), layer1Palette) }; } @@ -368,7 +344,7 @@ public class ChunkUtils { skull.despawnEntity(session, position); } - int blockId = BlockTranslator.getBedrockBlockId(blockState); + int blockId = session.getBlockTranslator().getBedrockBlockId(blockState); UpdateBlockPacket updateBlockPacket = new UpdateBlockPacket(); updateBlockPacket.setDataLayer(0); @@ -382,9 +358,9 @@ public class ChunkUtils { waterPacket.setDataLayer(1); waterPacket.setBlockPosition(position); if (BlockTranslator.isWaterlogged(blockState)) { - waterPacket.setRuntimeId(BEDROCK_WATER_ID); + waterPacket.setRuntimeId(session.getBlockTranslator().getBedrockWaterId()); } else { - waterPacket.setRuntimeId(BEDROCK_AIR_ID); + waterPacket.setRuntimeId(session.getBlockTranslator().getBedrockAirId()); } session.sendUpstreamPacket(waterPacket); @@ -417,7 +393,7 @@ public class ChunkUtils { data.setChunkX(chunkX + x); data.setChunkZ(chunkZ + z); data.setSubChunksLength(0); - data.setData(EMPTY_LEVEL_CHUNK_DATA); + data.setData(session.getBlockTranslator().getEmptyChunkData()); data.setCachingEnabled(false); session.sendUpstreamPacket(data); diff --git a/connector/src/main/resources/bedrock/blockpalette.1_16_100.nbt b/connector/src/main/resources/bedrock/blockpalette.1_16_100.nbt new file mode 100644 index 0000000000000000000000000000000000000000..4513be031b02c9a0b67871b36f14bb136ff30e04 GIT binary patch literal 104176 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 C)))!^ literal 0 HcmV?d00001 diff --git a/connector/src/main/resources/bedrock/blockpalette.nbt b/connector/src/main/resources/bedrock/blockpalette.nbt deleted file mode 100644 index b92a06260912dfd8e741a6c8f559af277d6c9775..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1160789 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 diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index bf0610450..ef994d8fe 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit bf0610450ce94507a18286e94af2965550ff9eaa +Subproject commit ef994d8fea421524cbc11f565a3f5ec59fc05741 From f0e6e55565c4b31599b80fff93b547a9b59697b0 Mon Sep 17 00:00:00 2001 From: YHDiamond <47502993+YHDiamond@users.noreply.github.com> Date: Tue, 9 Mar 2021 13:15:52 -0500 Subject: [PATCH 138/161] Update README.md (#2020) Co-authored-by: yehudahrrs <47502993+yehudahrrs@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 343fca94a..b17eede96 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,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 now joined us here! -### Currently supporting Minecraft Bedrock v1.16.100 - v1.16.201 and Minecraft Java v1.16.4 - v1.16.5. +### Currently supporting Minecraft Bedrock v1.16.100 - v1.16.210 and Minecraft Java v1.16.4 - v1.16.5. ## Setting Up Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set up Geyser. From 370f3c18ba4e014a8894627da9a2ae662c791b3d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 10 Mar 2021 13:54:09 -0500 Subject: [PATCH 139/161] Use compute for determining if a lectern if present Reduces having to call both containsKey and get. --- .../world/block/BlockStateValues.java | 9 ++++- .../geysermc/connector/utils/ChunkUtils.java | 37 ++++++++++--------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java index ebc90b722..ff51562b9 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockStateValues.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.JsonNode; import it.unimi.dsi.fastutil.ints.*; import java.util.Map; +import java.util.function.BiFunction; /** * Used for block entities if the Java block state contains Bedrock block information. @@ -199,7 +200,13 @@ public class BlockStateValues { return FLOWER_POT_VALUES; } - public static Int2BooleanMap getLecternBookStates() { + /** + * This returns a Map interface so IntelliJ doesn't complain about {@link Int2BooleanMap#compute(int, BiFunction)} + * not returning null. + * + * @return the lectern book state map pointing to book present state + */ + public static Map getLecternBookStates() { return LECTERN_BOOK_STATES; } diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index b3a31e1ab..7d5b56615 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -372,27 +372,30 @@ public class ChunkUtils { } session.sendUpstreamPacket(waterPacket); - if (BlockStateValues.getLecternBookStates().containsKey(blockState)) { - boolean lecternCachedHasBook = session.getLecternCache().contains(position); - boolean newLecternHasBook = BlockStateValues.getLecternBookStates().get(blockState); - if (!session.getConnector().getWorldManager().shouldExpectLecternHandled() && lecternCachedHasBook != newLecternHasBook) { - // Refresh the block entirely - it either has a book or no longer has a book - NbtMap newLecternTag; - if (newLecternHasBook) { - newLecternTag = session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false); + BlockStateValues.getLecternBookStates().compute(blockState, (key, newLecternHasBook) -> { + // Determine if this block is a lectern + if (newLecternHasBook != null) { + boolean lecternCachedHasBook = session.getLecternCache().contains(position); + if (!session.getConnector().getWorldManager().shouldExpectLecternHandled() && lecternCachedHasBook != newLecternHasBook) { + // Refresh the block entirely - it either has a book or no longer has a book + NbtMap newLecternTag; + if (newLecternHasBook) { + newLecternTag = session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false); + } else { + session.getLecternCache().remove(position); + newLecternTag = LecternInventoryTranslator.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 0).build(); + } + BlockEntityUtils.updateBlockEntity(session, newLecternTag, position); } else { - session.getLecternCache().remove(position); - newLecternTag = LecternInventoryTranslator.getBaseLecternTag(position.getX(), position.getY(), position.getZ(), 0).build(); + // As of right now, no tag can be added asynchronously + session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false); } - BlockEntityUtils.updateBlockEntity(session, newLecternTag, position); } else { - // As of right now, no tag can be added asynchronously - session.getConnector().getWorldManager().getLecternDataAt(session, position.getX(), position.getY(), position.getZ(), false); + // Lectern has been destroyed, if it existed + session.getLecternCache().remove(position); } - } else { - // Lectern has been destroyed, if it existed - session.getLecternCache().remove(position); - } + return newLecternHasBook; + }); // Since Java stores bed colors/skull information as part of the namespaced ID and Bedrock stores it as a tag // This is the only place I could find that interacts with the Java block state and block updates From e4934152417f4f127f0f85eb11e1159ed62d27a0 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 10 Mar 2021 14:43:54 -0500 Subject: [PATCH 140/161] More cartography parity fixes --- .../inventory/InventoryTranslator.java | 25 +++++++++++++------ .../CartographyInventoryTranslator.java | 8 +++--- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 1eef679f5..6ef214067 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -357,17 +357,28 @@ public abstract class InventoryTranslator { if (inventory instanceof CartographyContainer) { // TODO add this for more inventories? Only seems to glitch out the cartography table, though. ConsumeStackRequestActionData consumeData = (ConsumeStackRequestActionData) action; + int sourceSlot = bedrockSlotToJava(consumeData.getSource()); - if (sourceSlot == 0 && inventory.getItem(1).isEmpty()) { - // Java doesn't allow an item to be renamed; this is why CARTOGRAPHY_ADDITIONAL could remain empty for Bedrock - // We check this during slot 0 since setting the inventory slots here messes up shouldRejectItemPlace + if ((sourceSlot == 0 && inventory.getItem(1).isEmpty()) || (sourceSlot == 1 && inventory.getItem(0).isEmpty())) { + // Java doesn't allow an item to be renamed; this is why one of the slots could remain empty for Bedrock + // We check this now since setting the inventory slots here messes up shouldRejectItemPlace return rejectRequest(request, false); } - GeyserItemStack item = inventory.getItem(sourceSlot); - item.setAmount(item.getAmount() - consumeData.getCount()); - if (item.isEmpty()) { - inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session); + if (sourceSlot == 1) { + // Decrease the item count, but only after both slots are checked. + // Otherwise, the slot 1 check will fail + GeyserItemStack item = inventory.getItem(sourceSlot); + item.setAmount(item.getAmount() - consumeData.getCount()); + if (item.isEmpty()) { + inventory.setItem(sourceSlot, GeyserItemStack.EMPTY, session); + } + + GeyserItemStack itemZero = inventory.getItem(0); + itemZero.setAmount(itemZero.getAmount() - consumeData.getCount()); + if (itemZero.isEmpty()) { + inventory.setItem(0, GeyserItemStack.EMPTY, session); + } } affectedSlots.add(sourceSlot); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java index 319d9ec0a..f0f8b6d84 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java @@ -46,13 +46,13 @@ public class CartographyInventoryTranslator extends AbstractBlockInventoryTransl public boolean shouldRejectItemPlace(GeyserSession session, Inventory inventory, ContainerSlotType bedrockSourceContainer, int javaSourceSlot, ContainerSlotType bedrockDestinationContainer, int javaDestinationSlot) { if (javaDestinationSlot == 0) { - // Bedrock Edition can use paper in slot 0 + // Bedrock Edition can use paper or an empty map in slot 0 GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot); - return itemStack.getItemEntry().getJavaIdentifier().equals("minecraft:paper"); + return itemStack.getItemEntry().getJavaIdentifier().equals("minecraft:paper") || itemStack.getItemEntry().getJavaIdentifier().equals("minecraft:map"); } else if (javaDestinationSlot == 1) { - // Bedrock Edition can use a compass to create locator maps in the ADDITIONAL slot + // Bedrock Edition can use a compass to create locator maps, or use a filled map, in the ADDITIONAL slot GeyserItemStack itemStack = javaSourceSlot == -1 ? session.getPlayerInventory().getCursor() : inventory.getItem(javaSourceSlot); - return itemStack.getItemEntry().getJavaIdentifier().equals("minecraft:compass"); + return itemStack.getItemEntry().getJavaIdentifier().equals("minecraft:compass") || itemStack.getItemEntry().getJavaIdentifier().equals("minecraft:filled_map"); } return false; } From 09105e00b21749e78f79993fa6d5e9d2d9c63f45 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 11 Mar 2021 21:11:46 -0500 Subject: [PATCH 141/161] Fix item frame dropping on 1.16.210 (#2029) For whatever reason, item frames use the PlayerActionPacket in any non-creative gamemode, but creative mode keeps existing behavior. This has the benefit of fixing adventure mode item frame item dropping. --- .../BedrockItemFrameDropItemTranslator.java | 9 ++++++--- .../entity/player/BedrockActionTranslator.java | 18 +++++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemFrameDropItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemFrameDropItemTranslator.java index e7915bff3..959d6dc29 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemFrameDropItemTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockItemFrameDropItemTranslator.java @@ -28,20 +28,23 @@ package org.geysermc.connector.network.translators.bedrock; import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; import com.github.steveice10.mc.protocol.data.game.entity.player.InteractAction; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; -import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.ItemFrameDropItemPacket; import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; +/** + * Pre-1.16.210: used for both survival and creative item frame item removal + * + * 1.16.210: only used in creative. + */ @Translator(packet = ItemFrameDropItemPacket.class) public class BedrockItemFrameDropItemTranslator extends PacketTranslator { @Override public void translate(ItemFrameDropItemPacket packet, GeyserSession session) { - Vector3i position = Vector3i.from(packet.getBlockPosition().getX(), packet.getBlockPosition().getY(), packet.getBlockPosition().getZ()); - ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) ItemFrameEntity.getItemFrameEntityId(session, position), + ClientPlayerInteractEntityPacket interactPacket = new ClientPlayerInteractEntityPacket((int) ItemFrameEntity.getItemFrameEntityId(session, packet.getBlockPosition()), InteractAction.ATTACK, Hand.MAIN_HAND, session.isSneaking()); session.sendDownstreamPacket(interactPacket); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java index e837a036a..3079b668d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java @@ -27,12 +27,11 @@ package org.geysermc.connector.network.translators.bedrock.entity.player; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; -import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; -import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerState; +import com.github.steveice10.mc.protocol.data.game.entity.player.*; import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerAbilitiesPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3i; @@ -44,6 +43,7 @@ import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; import com.nukkitx.protocol.bedrock.packet.PlayerActionPacket; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.ItemFrameEntity; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; @@ -185,6 +185,18 @@ public class BedrockActionTranslator extends PacketTranslator Date: Thu, 11 Mar 2021 17:43:08 -0900 Subject: [PATCH 142/161] Fix loom output --- .../translators/LoomInventoryTranslator.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java index 38758c5f8..17c93c15b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LoomInventoryTranslator.java @@ -44,6 +44,7 @@ import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.SlotType; import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; import org.geysermc.connector.network.translators.item.translators.BannerTranslator; @@ -144,7 +145,7 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { ClientClickWindowButtonPacket packet = new ClientClickWindowButtonPacket(inventory.getId(), index); session.sendDownstreamPacket(packet); - GeyserItemStack inputCopy = inventory.getItem(0).copy(); + GeyserItemStack inputCopy = inventory.getItem(0).copy(1); inputCopy.setNetId(session.getNextItemNetId()); // Add the pattern manually, for better item synchronization if (inputCopy.getNbt() == null) { @@ -219,4 +220,12 @@ public class LoomInventoryTranslator extends AbstractBlockInventoryTranslator { } return super.javaSlotToBedrock(slot); } + + @Override + public SlotType getSlotType(int javaSlot) { + if (javaSlot == 3) { + return SlotType.OUTPUT; + } + return super.getSlotType(javaSlot); + } } From 821e3126ea3deb1cea616741a7eed04eb6e2dac7 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 12 Mar 2021 11:51:37 -0500 Subject: [PATCH 143/161] Handful of review fixes --- .../inventory/Generic3X3Container.java | 5 ++- .../BedrockLecternUpdateTranslator.java | 4 +-- .../inventory/InventoryTranslator.java | 33 ++++++++++++------- .../LecternInventoryTranslator.java | 10 +----- .../AbstractHorseInventoryTranslator.java | 3 -- .../inventory/updater/InventoryUpdater.java | 2 +- .../java/window/JavaSetSlotTranslator.java | 1 - .../world/block/BlockTranslator.java | 6 ++-- 8 files changed, 33 insertions(+), 31 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java b/connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java index 8c89cdeb6..080e11982 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Generic3X3Container.java @@ -27,10 +27,13 @@ package org.geysermc.connector.inventory; import com.github.steveice10.mc.protocol.data.game.window.WindowType; import lombok.Getter; +import org.geysermc.connector.network.session.GeyserSession; public class Generic3X3Container extends Container { /** - * Whether we need to set the container type as {@link com.nukkitx.protocol.bedrock.data.inventory.ContainerType#DROPPER} + * Whether we need to set the container type as {@link com.nukkitx.protocol.bedrock.data.inventory.ContainerType#DROPPER}. + * + * Used at {@link org.geysermc.connector.network.translators.inventory.translators.Generic3X3InventoryTranslator#openInventory(GeyserSession, Inventory)} */ @Getter private boolean isDropper = false; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java index 99dcebed9..10af0b610 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java @@ -56,7 +56,7 @@ public class BedrockLecternUpdateTranslator extends PacketTranslator currentJavaPage) { for (int i = currentJavaPage; i < newJavaPage; i++) { ClientClickWindowButtonPacket clickButtonPacket = new ClientClickWindowButtonPacket(session.getOpenInventory().getId(), 2); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 6ef214067..6ec90d24d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -108,8 +108,8 @@ public abstract class InventoryTranslator { public abstract void updateInventory(GeyserSession session, Inventory inventory); public abstract void updateSlot(GeyserSession session, Inventory inventory, int slot); public abstract int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData); - public abstract int javaSlotToBedrock(int javaSlot); //TODO - public abstract BedrockContainerSlot javaSlotToBedrockContainer(int javaSlot); //TODO + 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, WindowType windowType, PlayerInventory playerInventory); @@ -138,7 +138,7 @@ public abstract class InventoryTranslator { * If {@link #shouldHandleRequestFirst(StackRequestActionData, Inventory)} returns true, this will be called */ public ItemStackResponsePacket.Response translateSpecialRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { - return null; + return rejectRequest(request); } public void translateRequests(GeyserSession session, Inventory inventory, List requests) { @@ -151,15 +151,22 @@ public abstract class InventoryTranslator { if (shouldHandleRequestFirst(firstAction, inventory)) { // Some special request that shouldn't be processed normally response = translateSpecialRequest(session, inventory, request); - } else if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE) { - response = translateCraftingRequest(session, inventory, request); - } else if (firstAction.getType() == StackRequestActionType.CRAFT_RECIPE_AUTO) { - response = translateAutoCraftingRequest(session, inventory, request); - } else if (firstAction.getType() == StackRequestActionType.CRAFT_CREATIVE) { - // This is also used for pulling items out of creative - response = translateCreativeRequest(session, inventory, request); } else { - response = translateRequest(session, inventory, request); + switch (firstAction.getType()) { + case CRAFT_RECIPE: + response = translateCraftingRequest(session, inventory, request); + break; + case CRAFT_RECIPE_AUTO: + response = translateAutoCraftingRequest(session, inventory, request); + break; + case CRAFT_CREATIVE: + // This is also used for pulling items out of creative + response = translateCreativeRequest(session, inventory, request); + break; + default: + response = translateRequest(session, inventory, request); + break; + } } } else { response = rejectRequest(request); @@ -693,8 +700,10 @@ public abstract class InventoryTranslator { return acceptRequest(request, makeContainerEntries(session, inventory, plan.getAffectedSlots())); } + /** + * Handled in {@link PlayerInventoryTranslator} + */ public ItemStackResponsePacket.Response translateCreativeRequest(GeyserSession session, Inventory inventory, ItemStackRequest request) { - // Handled in PlayerInventoryTranslator return rejectRequest(request); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java index dbbc418ba..c08dfd995 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/LecternInventoryTranslator.java @@ -51,22 +51,19 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { public LecternInventoryTranslator() { super(1); - this.updater = new LecternInventoryUpdater(); + this.updater = new InventoryUpdater(); } @Override public void prepareInventory(GeyserSession session, Inventory inventory) { - } @Override public void openInventory(GeyserSession session, Inventory inventory) { - } @Override public void closeInventory(GeyserSession session, Inventory inventory) { - } @Override @@ -81,7 +78,6 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { @Override public void updateInventory(GeyserSession session, Inventory inventory) { - } @Override @@ -171,8 +167,4 @@ public class LecternInventoryTranslator extends BaseInventoryTranslator { } return builder; } - - private static class LecternInventoryUpdater extends InventoryUpdater { - - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java index 6c6c9a0c2..0e365aca1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/horse/AbstractHorseInventoryTranslator.java @@ -41,17 +41,14 @@ public abstract class AbstractHorseInventoryTranslator extends BaseInventoryTran @Override public void prepareInventory(GeyserSession session, Inventory inventory) { - } @Override public void openInventory(GeyserSession session, Inventory inventory) { - } @Override public void closeInventory(GeyserSession session, Inventory inventory) { - } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java index d7c137177..e94c0944b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java @@ -35,7 +35,7 @@ import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import java.util.Arrays; -public abstract class InventoryUpdater { +public class InventoryUpdater { public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { ItemData[] bedrockItems = new ItemData[36]; for (int i = 0; i < 36; i++) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java index b5978ba76..a0e9901f3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaSetSlotTranslator.java @@ -134,7 +134,6 @@ public class JavaSetSlotTranslator extends PacketTranslator height += -firstRow + 1; width += -firstCol + 1; - //TODO recipes: for (Recipe recipe : session.getCraftingRecipes().values()) { if (recipe.getType() == RecipeType.CRAFTING_SHAPED) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java index ec1c79950..057c74d2b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/BlockTranslator.java @@ -112,8 +112,6 @@ public abstract class BlockTranslator { */ private final Map javaIdentifierToBedrockTag; - private static final int BLOCK_STATE_VERSION = 17825808; - /** * Stores the raw blocks JSON until it is no longer needed. */ @@ -413,6 +411,10 @@ public abstract class BlockTranslator { return bedrockWaterId; } + /** + * @return the "block state version" generated in the Bedrock block palette that completes an NBT indication of a + * block state. + */ public abstract int getBlockStateVersion(); public byte[] getEmptyChunkData() { From 23ab69702d56f97358129f305fdb44882506ad91 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 12 Mar 2021 17:07:29 -0500 Subject: [PATCH 144/161] More review fixes and make beacon more reliable --- .../holder/BlockInventoryHolder.java | 12 +++++++- .../BeaconInventoryTranslator.java | 29 ++++++++++++++++++- .../CartographyInventoryTranslator.java | 16 +++++----- .../java/world/JavaTradeListTranslator.java | 2 ++ 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java index e7bfd90f0..b7f67879b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -74,7 +74,7 @@ public class BlockInventoryHolder extends InventoryHolder { // 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 (session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition())) { + if (checkInteractionPosition(session)) { // Then, check to see if the interacted block is valid for this inventory by ensuring the block state identifier is valid int javaBlockId = session.getConnector().getWorldManager().getBlockAt(session, session.getLastInteractionBlockPosition()); String[] javaBlockString = BlockTranslator.getJavaIdBlockMap().inverse().getOrDefault(javaBlockId, "minecraft:air").split("\\["); @@ -101,6 +101,16 @@ public class BlockInventoryHolder extends InventoryHolder { setCustomName(session, position, inventory, defaultJavaBlockState); } + /** + * Will be overwritten in the beacon inventory translator to remove the check, since virtual inventories can't exist. + * + * @return if the player's last interaction position and current position match. Used to ensure that we don't select + * a block to hold the inventory that's wildly out of range. + */ + protected boolean checkInteractionPosition(GeyserSession session) { + return session.getLastInteractionPlayerPosition().equals(session.getPlayerEntity().getPosition()); + } + /** * @return true if this Java block ID can be used for player inventory. */ diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java index 46c09b66b..5af921f2d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BeaconInventoryTranslator.java @@ -44,13 +44,40 @@ import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.BedrockContainerSlot; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.inventory.holder.BlockInventoryHolder; import org.geysermc.connector.network.translators.inventory.updater.UIInventoryUpdater; +import org.geysermc.connector.utils.InventoryUtils; import java.util.Collections; public class BeaconInventoryTranslator extends AbstractBlockInventoryTranslator { public BeaconInventoryTranslator() { - super(1, "minecraft:beacon", ContainerType.BEACON, UIInventoryUpdater.INSTANCE); + super(1, new BlockInventoryHolder("minecraft:beacon", ContainerType.BEACON) { + @Override + public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + if (!session.getConnector().getConfig().isCacheChunks()) { + // Beacons cannot work without knowing their physical location + return; + } + super.prepareInventory(translator, session, inventory); + } + + @Override + protected boolean checkInteractionPosition(GeyserSession session) { + // Since we can't fall back to a virtual inventory, let's make opening one easier + return true; + } + + @Override + public void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + if (!session.getConnector().getConfig().isCacheChunks() || !((BeaconContainer) inventory).isUsingRealBlock()) { + InventoryUtils.closeInventory(session, inventory.getId(), false); + return; + } + super.openInventory(translator, session, inventory); + } + }, UIInventoryUpdater.INSTANCE); } @Override diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java index f0f8b6d84..a3b50dace 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CartographyInventoryTranslator.java @@ -59,14 +59,14 @@ public class CartographyInventoryTranslator extends AbstractBlockInventoryTransl @Override public int bedrockSlotToJava(StackRequestSlotInfoData slotInfoData) { - if (slotInfoData.getContainer() == ContainerSlotType.CARTOGRAPHY_INPUT) { - return 0; - } - if (slotInfoData.getContainer() == ContainerSlotType.CARTOGRAPHY_ADDITIONAL) { - return 1; - } - if (slotInfoData.getContainer() == ContainerSlotType.CARTOGRAPHY_RESULT || slotInfoData.getContainer() == ContainerSlotType.CREATIVE_OUTPUT) { - return 2; + switch (slotInfoData.getContainer()) { + case CARTOGRAPHY_INPUT: + return 0; + case CARTOGRAPHY_ADDITIONAL: + return 1; + case CARTOGRAPHY_RESULT: + case CREATIVE_OUTPUT: + return 2; } return super.bedrockSlotToJava(slotInfoData); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java index df8339079..d31dbb617 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaTradeListTranslator.java @@ -58,6 +58,7 @@ public class JavaTradeListTranslator extends PacketTranslator Date: Sat, 13 Mar 2021 15:01:55 -0500 Subject: [PATCH 145/161] Cleaner ItemEntry retrieval in BedrockInteractTranslator --- .../bedrock/entity/player/BedrockInteractTranslator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java index ca71a1975..e5c83388b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockInteractTranslator.java @@ -41,7 +41,6 @@ import org.geysermc.connector.entity.Entity; import org.geysermc.connector.entity.living.animal.horse.AbstractHorseEntity; import org.geysermc.connector.entity.living.animal.horse.HorseEntity; import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.inventory.GeyserItemStack; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -125,7 +124,7 @@ public class BedrockInteractTranslator extends PacketTranslator if (interactEntity == null) return; EntityDataMap entityMetadata = interactEntity.getMetadata(); - ItemEntry itemEntry = session.getPlayerInventory().getItemInHand() == GeyserItemStack.EMPTY ? ItemEntry.AIR : ItemRegistry.getItem(session.getPlayerInventory().getItemInHand().getItemStack()); + ItemEntry itemEntry = session.getPlayerInventory().getItemInHand().getItemEntry(); String javaIdentifierStripped = itemEntry.getJavaIdentifier().replace("minecraft:", ""); // TODO - in the future, update these in the metadata? So the client doesn't have to wiggle their cursor around for it to happen From 48aa586b218b791ead88cb57683e0889ecb682f1 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 13 Mar 2021 18:07:43 -0500 Subject: [PATCH 146/161] More formatting fixes --- .../manager/GeyserSpigotWorldManager.java | 1 + .../connector/inventory/GeyserItemStack.java | 12 ++---- .../BedrockLecternUpdateTranslator.java | 2 + .../player/BedrockInteractTranslator.java | 2 +- .../inventory/InventoryTranslator.java | 37 ++++++++++++------- .../CraftingInventoryTranslator.java | 3 +- .../updater/HorseInventoryUpdater.java | 1 - 7 files changed, 34 insertions(+), 24 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java index 13f696fd5..bc76288c5 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -235,6 +235,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { NbtMap blockEntityTag = lecternTag.build(); BlockEntityUtils.updateBlockEntity(session, blockEntityTag, Vector3i.from(x, y, z)); }; + if (isChunkLoad) { // Delay to ensure the chunk is sent first, and then the lectern data Bukkit.getScheduler().runTaskLater(this.plugin, lecternInfoGet, 5); diff --git a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java index 7cdaf1801..b4e91c1d6 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/GeyserItemStack.java @@ -62,6 +62,10 @@ public class GeyserItemStack { this.netId = netId; } + public static GeyserItemStack from(ItemStack itemStack) { + return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getNbt()); + } + public int getJavaId() { return isEmpty() ? 0 : javaId; } @@ -74,10 +78,6 @@ public class GeyserItemStack { return isEmpty() ? null : nbt; } - public void setNetId(int netId) { - this.netId = netId; - } - public int getNetId() { return isEmpty() ? 0 : netId; } @@ -90,10 +90,6 @@ public class GeyserItemStack { amount -= sub; } - public static GeyserItemStack from(ItemStack itemStack) { - return itemStack == null ? EMPTY : new GeyserItemStack(itemStack.getId(), itemStack.getAmount(), itemStack.getNbt()); - } - public ItemStack getItemStack() { return getItemStack(amount); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java index 10af0b610..ae99fec07 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockLecternUpdateTranslator.java @@ -65,6 +65,7 @@ public class BedrockLecternUpdateTranslator extends PacketTranslator if (ridingEntity instanceof AbstractHorseEntity) { if (ridingEntity.getMetadata().getFlags().getFlag(EntityFlag.TAMED)) { // We should request to open the horse inventory instead - ClientPlayerStatePacket openHorseWindowPacket = new ClientPlayerStatePacket((int)session.getPlayerEntity().getEntityId(), PlayerState.OPEN_HORSE_INVENTORY); + ClientPlayerStatePacket openHorseWindowPacket = new ClientPlayerStatePacket((int) session.getPlayerEntity().getEntityId(), PlayerState.OPEN_HORSE_INVENTORY); session.sendDownstreamPacket(openHorseWindowPacket); } } else { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 6ec90d24d..5fd44c8b1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -39,6 +39,7 @@ import com.nukkitx.protocol.bedrock.data.inventory.stackrequestactions.*; import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import it.unimi.dsi.fastutil.ints.*; import lombok.AllArgsConstructor; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.inventory.*; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.click.Click; @@ -171,9 +172,12 @@ public abstract class InventoryTranslator { } else { response = rejectRequest(request); } - if (response.getResult() == ItemStackResponsePacket.ResponseStatus.ERROR) { + + if (response.getResult() != ItemStackResponsePacket.ResponseStatus.OK) { + // Sync our copy of the inventory with Bedrock's to prevent desyncs refresh = true; } + responsePacket.getEntries().add(response); } session.sendUpstreamPacket(responsePacket); @@ -198,11 +202,10 @@ public abstract class InventoryTranslator { transferAction.getSource().getSlot() >= 28 && transferAction.getSource().getSlot() <= 31) { return rejectRequest(request, false); } - session.getConnector().getLogger().error("DEBUG: About to reject TAKE/PLACE request made by " + session.getName()); - session.getConnector().getLogger().error("Source: " + transferAction.getSource().toString() + " Result: " + checkNetId(session, inventory, transferAction.getSource())); - session.getConnector().getLogger().error("Destination: " + transferAction.getDestination().toString() + " Result: " + checkNetId(session, inventory, transferAction.getDestination())); - session.getConnector().getLogger().error("Geyser's record of source slot: " + inventory.getItem(bedrockSlotToJava(transferAction.getSource()))); - session.getConnector().getLogger().error("Geyser's record of destination slot: " + inventory.getItem(bedrockSlotToJava(transferAction.getDestination()))); + if (session.getConnector().getConfig().isDebugMode()) { + session.getConnector().getLogger().error("DEBUG: About to reject TAKE/PLACE request made by " + session.getName()); + dumpStackRequestDetails(session, inventory, transferAction.getSource(), transferAction.getDestination()); + } return rejectRequest(request); } @@ -285,11 +288,10 @@ public abstract class InventoryTranslator { case SWAP: { SwapStackRequestActionData swapAction = (SwapStackRequestActionData) action; if (!(checkNetId(session, inventory, swapAction.getSource()) && checkNetId(session, inventory, swapAction.getDestination()))) { - session.getConnector().getLogger().error("DEBUG: About to reject SWAP request made by " + session.getName()); - session.getConnector().getLogger().error("Source: " + swapAction.getSource().toString() + " Result: " + checkNetId(session, inventory, swapAction.getSource())); - session.getConnector().getLogger().error("Destination: " + swapAction.getDestination().toString() + " Result: " + checkNetId(session, inventory, swapAction.getDestination())); - session.getConnector().getLogger().error("Geyser's record of source slot: " + inventory.getItem(bedrockSlotToJava(swapAction.getSource()))); - session.getConnector().getLogger().error("Geyser's record of destination slot: " + inventory.getItem(bedrockSlotToJava(swapAction.getDestination()))); + if (session.getConnector().getConfig().isDebugMode()) { + session.getConnector().getLogger().error("DEBUG: About to reject SWAP request made by " + session.getName()); + dumpStackRequestDetails(session, inventory, swapAction.getSource(), swapAction.getDestination()); + } return rejectRequest(request); } @@ -756,13 +758,22 @@ public abstract class InventoryTranslator { * as bad (false). */ public static ItemStackResponsePacket.Response rejectRequest(ItemStackRequest request, boolean throwError) { - if (throwError) { - // Currently for debugging, but might be worth it to keep in the future if something goes terribly wrong. + if (throwError && GeyserConnector.getInstance().getConfig().isDebugMode()) { new Throwable("DEBUGGING: ItemStackRequest rejected " + request.toString()).printStackTrace(); } return new ItemStackResponsePacket.Response(ItemStackResponsePacket.ResponseStatus.ERROR, request.getRequestId(), Collections.emptyList()); } + /** + * Print out the contents of an ItemStackRequest, should the net ID check fail. + */ + protected void dumpStackRequestDetails(GeyserSession session, Inventory inventory, StackRequestSlotInfoData source, StackRequestSlotInfoData destination) { + session.getConnector().getLogger().error("Source: " + source.toString() + " Result: " + checkNetId(session, inventory, source)); + session.getConnector().getLogger().error("Destination: " + destination.toString() + " Result: " + checkNetId(session, inventory, destination)); + session.getConnector().getLogger().error("Geyser's record of source slot: " + inventory.getItem(bedrockSlotToJava(source))); + session.getConnector().getLogger().error("Geyser's record of destination slot: " + inventory.getItem(bedrockSlotToJava(destination))); + } + public boolean checkNetId(GeyserSession session, Inventory inventory, StackRequestSlotInfoData slotInfoData) { int netId = slotInfoData.getStackNetworkId(); // "In my testing, sometimes the client thinks the netId of an item in the crafting grid is 1, even though we never said it was. diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java index 81769c00a..363c9b702 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/CraftingInventoryTranslator.java @@ -39,8 +39,9 @@ public class CraftingInventoryTranslator extends AbstractBlockInventoryTranslato @Override public SlotType getSlotType(int javaSlot) { - if (javaSlot == 0) + if (javaSlot == 0) { return SlotType.OUTPUT; + } return SlotType.NORMAL; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java index d238b4148..db067a74c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/HorseInventoryUpdater.java @@ -64,5 +64,4 @@ public class HorseInventoryUpdater extends InventoryUpdater { session.sendUpstreamPacket(slotPacket); return true; } - } From e71ffa455f86e7032b4ea5095dae98412d96d288 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sat, 13 Mar 2021 23:09:37 -0500 Subject: [PATCH 147/161] Fix brewing stand fuel input --- .../inventory/translators/BrewingInventoryTranslator.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java index 992a74511..c54722849 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/translators/BrewingInventoryTranslator.java @@ -91,6 +91,8 @@ public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator return 3; case 3: return 0; + case 4: + return 4; } return super.javaSlotToBedrock(slot); } @@ -105,7 +107,7 @@ public class BrewingInventoryTranslator extends AbstractBlockInventoryTranslator case 3: return new BedrockContainerSlot(ContainerSlotType.BREWING_INPUT, 0); case 4: - return new BedrockContainerSlot(ContainerSlotType.BREWING_INPUT, 0); + return new BedrockContainerSlot(ContainerSlotType.BREWING_FUEL, 4); } return super.javaSlotToBedrockContainer(slot); } From ba64a7a4892670486e0bb77668ef0c957426170d Mon Sep 17 00:00:00 2001 From: toinouH Date: Sun, 14 Mar 2021 16:37:10 +0100 Subject: [PATCH 148/161] Update Adventure from 4.5.0 to 4.7.0 (#2037) * Update Adventure from 4.5.0 to 4.7.0 * Fix test failure due to changes in Adventure color mapping Co-authored-by: rtm516 --- connector/pom.xml | 2 +- .../network/translators/chat/MessageTranslatorTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/connector/pom.xml b/connector/pom.xml index fb253117c..cb528e27b 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -13,7 +13,7 @@ 4.1.59.Final 8.5.2 - 4.5.0 + 4.7.0 diff --git a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java index bbad2394d..7052123fe 100644 --- a/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java +++ b/connector/src/test/java/org/geysermc/connector/network/translators/chat/MessageTranslatorTest.java @@ -46,8 +46,8 @@ public class MessageTranslatorTest { // RGB downgrade test messages.put("{\"extra\":[{\"text\":\" \"},{\"color\":\"gold\",\"text\":\"The \"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"#3AA9FF\",\"bold\":true,\"text\":\"CubeCraft\"},{\"color\":\"#E14248\",\"obfuscated\":true,\"text\":\"||\"},{\"color\":\"gold\",\"text\":\" Network \"},{\"color\":\"green\",\"text\":\"[1.8/1.9+]\\n \"},{\"color\":\"#f5e342\",\"text\":\"✦ \"},{\"color\":\"#b042f5\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#c142f5\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#d342f5\",\"bold\":true,\"text\":\"W\"},{\"color\":\"#e442f5\",\"bold\":true,\"text\":\":\"},{\"color\":\"#f542f5\",\"bold\":true,\"text\":\" \"},{\"color\":\"#bcf542\",\"bold\":true,\"text\":\"A\"},{\"color\":\"#acee3f\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#9ce73c\",\"bold\":true,\"text\":\"O\"},{\"color\":\"#8ce039\",\"bold\":true,\"text\":\"N\"},{\"color\":\"#7cd936\",\"bold\":true,\"text\":\"G\"},{\"color\":\"#6cd233\",\"bold\":true,\"text\":\" \"},{\"color\":\"#5ccb30\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#4cc42d\",\"bold\":true,\"text\":\"L\"},{\"color\":\"#3cbd2a\",\"bold\":true,\"text\":\"I\"},{\"color\":\"#2cb627\",\"bold\":true,\"text\":\"M\"},{\"color\":\"#1caf24\",\"bold\":true,\"text\":\"E\"},{\"color\":\"#0ca821\",\"bold\":true,\"text\":\"S\"},{\"color\":\"#f5e342\",\"text\":\" \"},{\"color\":\"#6d7c87\",\"text\":\"(kinda sus) \"},{\"color\":\"#f5e342\",\"text\":\"✦\"}],\"text\":\"\"}", - " §r§6The §r§c§k||§r§3§lCubeCraft§r§c§k||§r§6 Network §r§a[1.8/1.9+]\n" + - " §r§e✦ §r§d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§a§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§a§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §r§8(kinda sus) §r§e✦"); + " §r§6The §r§d§k||§r§b§lCubeCraft§r§d§k||§r§6 Network §r§a[1.8/1.9+]\n" + + " §r§e✦ §r§d§lN§r§d§lE§r§d§lW§r§d§l:§r§d§l §r§e§lA§r§e§lM§r§e§lO§r§a§lN§r§a§lG§r§a§l §r§a§lS§r§2§lL§r§2§lI§r§2§lM§r§2§lE§r§2§lS§r§e §r§b(kinda sus) §r§e✦"); // Color code format resetting messages.put("{\"text\":\"\",\"extra\":[{\"text\":\"\",\"extra\":[{\"text\":\"[\",\"color\":\"gray\"},{\"text\":\"H\",\"color\":\"yellow\"},{\"text\":\"]\",\"color\":\"gray\"},{\"text\":\" \",\"color\":\"white\"},{\"text\":\"GUEST\",\"color\":\"#b7b7b7\",\"bold\":true}]},{\"text\":\"\",\"extra\":[{\"text\":\" \",\"bold\":true},{\"text\":\"»\",\"color\":\"blue\"},{\"text\":\" \",\"color\":\"gray\"}]},{\"text\":\"\",\"extra\":[{\"text\":\"rtm516\",\"color\":\"white\"},{\"text\":\": \",\"color\":\"gray\"},{\"text\":\"\",\"color\":\"white\"}]},{\"text\":\"\",\"extra\":[{\"text\":\"This is an amazing bedrock test message\",\"color\":\"white\"}]}]}\n", From 1d8961c498229b9e4f480f834a88cdb4a51701cb Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 14 Mar 2021 12:26:47 -0400 Subject: [PATCH 149/161] Allow GeyserWorldManager to be overwritten while still holding a cache (#2036) --- .../manager/GeyserSpigotFallbackWorldManager.java | 2 +- .../spigot/world/manager/GeyserSpigotWorldManager.java | 2 +- .../connector/network/session/cache/ChunkCache.java | 10 +++++----- .../network/translators/world/GeyserWorldManager.java | 2 +- .../network/translators/world/WorldManager.java | 4 ++-- .../java/org/geysermc/connector/utils/ChunkUtils.java | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java index 4cac791a0..a9de94db5 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotFallbackWorldManager.java @@ -52,7 +52,7 @@ public class GeyserSpigotFallbackWorldManager extends GeyserSpigotWorldManager { } @Override - public boolean hasMoreBlockDataThanChunkCache() { + public boolean hasOwnChunkCache() { return false; } diff --git a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java index bc76288c5..ba61eeb72 100644 --- a/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java +++ b/bootstrap/spigot/src/main/java/org/geysermc/platform/spigot/world/manager/GeyserSpigotWorldManager.java @@ -140,7 +140,7 @@ public class GeyserSpigotWorldManager extends GeyserWorldManager { } @Override - public boolean hasMoreBlockDataThanChunkCache() { + public boolean hasOwnChunkCache() { return true; } diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java index a48b20cee..bf1f4718e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java @@ -29,7 +29,6 @@ import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.chunk.Column; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import org.geysermc.connector.bootstrap.GeyserBootstrap; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.MathUtils; @@ -38,14 +37,15 @@ public class ChunkCache { private final boolean cache; - private final Long2ObjectMap chunks = new Long2ObjectOpenHashMap<>(); + private final Long2ObjectMap chunks; public ChunkCache(GeyserSession session) { - if (session.getConnector().getWorldManager().getClass() == GeyserBootstrap.DEFAULT_CHUNK_MANAGER.getClass()) { - this.cache = session.getConnector().getConfig().isCacheChunks(); - } else { + if (session.getConnector().getWorldManager().hasOwnChunkCache()) { this.cache = false; // To prevent Spigot from initializing + } else { + this.cache = session.getConnector().getConfig().isCacheChunks(); } + chunks = cache ? new Long2ObjectOpenHashMap<>() : null; } public Column addToCache(Column chunk) { diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java index 6d2d8720d..014f3e366 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/GeyserWorldManager.java @@ -73,7 +73,7 @@ public class GeyserWorldManager extends WorldManager { } @Override - public boolean hasMoreBlockDataThanChunkCache() { + public boolean hasOwnChunkCache() { // This implementation can only fetch data from the session chunk cache return false; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java index 6795ae4bf..e97dcec32 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/WorldManager.java @@ -88,14 +88,14 @@ public abstract class WorldManager { public abstract void getBlocksInSection(GeyserSession session, int x, int y, int z, Chunk section); /** - * Checks whether or not this world manager has access to more block data than the chunk cache. + * Checks whether or not this world manager requires a separate chunk cache/has access to more block data than the chunk cache. *

* Some world managers (e.g. Spigot) can provide access to block data outside of the chunk cache, and even with chunk caching disabled. This * method provides a means to check if this manager has this capability. * * @return whether or not this world manager has access to more block data than the chunk cache */ - public abstract boolean hasMoreBlockDataThanChunkCache(); + public abstract boolean hasOwnChunkCache(); /** * Gets the Java biome data for the specified chunk. diff --git a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java index 7d5b56615..b6e387237 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -91,7 +91,7 @@ public class ChunkUtils { BitSet waterloggedPaletteIds = new BitSet(); BitSet pistonOrFlowerPaletteIds = new BitSet(); - boolean worldManagerHasMoreBlockDataThanCache = session.getConnector().getWorldManager().hasMoreBlockDataThanChunkCache(); + boolean worldManagerHasMoreBlockDataThanCache = session.getConnector().getWorldManager().hasOwnChunkCache(); // If the received packet was a full chunk update, null sections in the chunk are guaranteed to also be null in the world manager boolean shouldCheckWorldManagerOnMissingSections = isNonFullChunk && worldManagerHasMoreBlockDataThanCache; From 2f058e05bdf63cb9501c43ad805c66d15f168e01 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 14 Mar 2021 12:52:09 -0400 Subject: [PATCH 150/161] Allow dyed horse leather armor to translate (#2016) --- .../nbt/LeatherArmorTranslator.java | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java index f78eadc25..c2305738d 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/translators/nbt/LeatherArmorTranslator.java @@ -35,29 +35,28 @@ import org.geysermc.connector.network.translators.item.ItemEntry; @ItemRemapper public class LeatherArmorTranslator extends NbtItemStackTranslator { - private static final String[] ITEMS = new String[]{"minecraft:leather_helmet", "minecraft:leather_chestplate", "minecraft:leather_leggings", "minecraft:leather_boots"}; + private static final String[] ITEMS = new String[]{"minecraft:leather_helmet", "minecraft:leather_chestplate", + "minecraft:leather_leggings", "minecraft:leather_boots", "minecraft:leather_horse_armor"}; @Override public void translateToBedrock(GeyserSession session, CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("display")) { + CompoundTag displayTag = itemTag.get("display"); + if (displayTag == null) { return; } - CompoundTag displayTag = itemTag.get("display"); - if (displayTag.contains("color")) { - IntTag color = displayTag.get("color"); - if (color != null) { - itemTag.put(new IntTag("customColor", color.getValue())); - displayTag.remove("color"); - } + IntTag color = displayTag.get("color"); + if (color != null) { + itemTag.put(new IntTag("customColor", color.getValue())); + displayTag.remove("color"); } } @Override public void translateToJava(CompoundTag itemTag, ItemEntry itemEntry) { - if (!itemTag.contains("customColor")) { + IntTag color = itemTag.get("customColor"); + if (color == null) { return; } - IntTag color = itemTag.get("customColor"); CompoundTag displayTag = itemTag.get("display"); if (displayTag == null) { displayTag = new CompoundTag("display"); From d41d8b0ebfb8bf1ec27e3c4319b8a6f8f112955b Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Mon, 15 Mar 2021 14:49:44 -0400 Subject: [PATCH 151/161] Fix item durability 'resetting' on 1.16.210 (#2042) --- .../inventory/InventoryTranslator.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java index 5fd44c8b1..4a45b5c9f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/InventoryTranslator.java @@ -32,6 +32,8 @@ import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapedRecipeData; import com.github.steveice10.mc.protocol.data.game.recipe.data.ShapelessRecipeData; import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.inventory.ContainerSlotType; import com.nukkitx.protocol.bedrock.data.inventory.ItemStackRequest; import com.nukkitx.protocol.bedrock.data.inventory.StackRequestSlotInfoData; @@ -40,7 +42,10 @@ import com.nukkitx.protocol.bedrock.packet.ItemStackResponsePacket; import it.unimi.dsi.fastutil.ints.*; import lombok.AllArgsConstructor; import org.geysermc.connector.GeyserConnector; -import org.geysermc.connector.inventory.*; +import org.geysermc.connector.inventory.CartographyContainer; +import org.geysermc.connector.inventory.GeyserItemStack; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.inventory.click.Click; import org.geysermc.connector.network.translators.inventory.click.ClickPlan; @@ -854,7 +859,17 @@ public abstract class InventoryTranslator { public static ItemStackResponsePacket.ItemEntry makeItemEntry(int bedrockSlot, GeyserItemStack itemStack) { ItemStackResponsePacket.ItemEntry itemEntry; if (!itemStack.isEmpty()) { - itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) itemStack.getAmount(), itemStack.getNetId(), "", 0); + // As of 1.16.210: Bedrock needs confirmation on what the current item durability is. + // If 0 is sent, then Bedrock thinks the item is not damaged + int durability = 0; + if (itemStack.getNbt() != null) { + Tag damage = itemStack.getNbt().get("Damage"); + if (damage instanceof IntTag) { + durability = ((IntTag) damage).getValue(); + } + } + + itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) itemStack.getAmount(), itemStack.getNetId(), "", durability); } else { itemEntry = new ItemStackResponsePacket.ItemEntry((byte) bedrockSlot, (byte) bedrockSlot, (byte) 0, 0, "", 0); } From 3d4fff8dee92635f3e83ea8efe3feb560e9bda10 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Wed, 17 Mar 2021 10:38:56 -0400 Subject: [PATCH 152/161] Fix sleeping on older Minecraft server versions (#2049) BED_POSITION is enough to trigger sleep. --- .../main/java/org/geysermc/connector/entity/Entity.java | 3 --- .../java/org/geysermc/connector/entity/LivingEntity.java | 7 +++++++ .../java/entity/JavaEntityMetadataTranslator.java | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 741f5fcd0..30e63f33c 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -331,15 +331,12 @@ public class Entity { case 6: // Pose change if (entityMetadata.getValue().equals(Pose.SLEEPING)) { metadata.getFlags().setFlag(EntityFlag.SLEEPING, true); - // Has to be a byte or it does not work - metadata.put(EntityData.PLAYER_FLAGS, (byte) 2); metadata.put(EntityData.BOUNDING_BOX_WIDTH, 0.2f); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, 0.2f); } else if (metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { metadata.getFlags().setFlag(EntityFlag.SLEEPING, false); metadata.put(EntityData.BOUNDING_BOX_WIDTH, getEntityType().getWidth()); metadata.put(EntityData.BOUNDING_BOX_HEIGHT, getEntityType().getHeight()); - metadata.put(EntityData.PLAYER_FLAGS, (byte) 0); } break; } diff --git a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java index f38f1e6b2..4dc0998aa 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/LivingEntity.java @@ -99,6 +99,13 @@ public class LivingEntity extends Entity { // Bed has to be updated, or else player is floating in the air ChunkUtils.updateBlock(session, bed, bedPosition); } + // Indicate that the player should enter the sleep cycle + // Has to be a byte or it does not work + // (Bed position is what actually triggers sleep - "pose" is only optional) + metadata.put(EntityData.PLAYER_FLAGS, (byte) 2); + } else { + // Player is no longer sleeping + metadata.put(EntityData.PLAYER_FLAGS, (byte) 0); } break; } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityMetadataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityMetadataTranslator.java index e3c64d55f..3c4153df3 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityMetadataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityMetadataTranslator.java @@ -39,9 +39,11 @@ public class JavaEntityMetadataTranslator extends PacketTranslator Date: Wed, 17 Mar 2021 11:15:57 -0400 Subject: [PATCH 153/161] SRV improvements and improvements for GeyserConnect (#2048) - Individual per-player remote, port, and address - Removal of RemoteServer class - Do SRV lookup on startup and that's it --- connector/pom.xml | 4 +- .../geysermc/connector/GeyserConnector.java | 13 +++--- .../network/UpstreamPacketHandler.java | 4 +- .../network/remote/RemoteServer.java | 37 ---------------- .../network/session/GeyserSession.java | 42 ++++++++++++++----- .../geysermc/connector/skin/SkinManager.java | 2 +- 6 files changed, 41 insertions(+), 61 deletions(-) delete mode 100644 connector/src/main/java/org/geysermc/connector/network/remote/RemoteServer.java diff --git a/connector/pom.xml b/connector/pom.xml index cb528e27b..6f2cdab4e 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -140,9 +140,9 @@ - com.github.steveice10 + com.github.GeyserMC PacketLib - 54f761c + b77a427 compile diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 0b7f84646..f0dd696f6 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -40,7 +40,6 @@ import org.geysermc.connector.common.AuthType; import org.geysermc.connector.configuration.GeyserConfiguration; import org.geysermc.connector.metrics.Metrics; import org.geysermc.connector.network.ConnectorServerEventHandler; -import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.BiomeTranslator; import org.geysermc.connector.network.translators.EntityIdentifierRegistry; @@ -97,9 +96,8 @@ public class GeyserConnector { private static GeyserConnector instance; - private RemoteServer remoteServer; @Setter - private AuthType authType; + private AuthType defaultAuthType; private boolean shuttingDown = false; @@ -166,7 +164,7 @@ public class GeyserConnector { String remoteAddress = config.getRemote().getAddress(); int remotePort = config.getRemote().getPort(); // Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry. - if ((config.isLegacyPingPassthrough() || platformType == PlatformType.STANDALONE) && !remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) { + if (!remoteAddress.matches(IP_REGEX) && !remoteAddress.equalsIgnoreCase("localhost")) { try { // Searches for a server address and a port from a SRV record of the specified host name InitialDirContext ctx = new InitialDirContext(); @@ -186,8 +184,7 @@ public class GeyserConnector { } } - remoteServer = new RemoteServer(config.getRemote().getAddress(), remotePort); - authType = AuthType.getByName(config.getRemote().getAuthType()); + defaultAuthType = AuthType.getByName(config.getRemote().getAuthType()); CooldownUtils.setShowCooldown(config.isShowCooldown()); DimensionUtils.changeBedrockNetherId(config.isAboveBedrockNetherBuilding()); // Apply End dimension ID workaround to Nether @@ -334,8 +331,7 @@ public class GeyserConnector { generalThreadPool.shutdown(); bedrockServer.close(); players.clear(); - remoteServer = null; - authType = null; + defaultAuthType = null; this.getCommandManager().getCommands().clear(); bootstrap.getGeyserLogger().info(LanguageUtils.getLocaleStringLog("geyser.core.shutdown.done")); @@ -371,6 +367,7 @@ public class GeyserConnector { * @param xuid the Xbox user identifier * @return the player or null if there is no player online with this xuid */ + @SuppressWarnings("unused") // API usage public GeyserSession getPlayerByXuid(String xuid) { for (GeyserSession session : players) { if (session.getAuthData() != null && session.getAuthData().getXboxUUID().equals(xuid)) { diff --git a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java index 829ae23ef..c85bb773b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java +++ b/connector/src/main/java/org/geysermc/connector/network/UpstreamPacketHandler.java @@ -97,7 +97,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { public boolean handle(ResourcePackClientResponsePacket packet) { switch (packet.getStatus()) { case COMPLETED: - session.connect(connector.getRemoteServer()); + session.connect(); connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.connect", session.getAuthData().getName())); break; @@ -186,7 +186,7 @@ public class UpstreamPacketHandler extends LoggingPacketHandler { public boolean handle(SetLocalPlayerAsInitializedPacket packet) { LanguageUtils.loadGeyserLocale(session.getLocale()); - if (!session.isLoggedIn() && !session.isLoggingIn() && session.getConnector().getAuthType() == AuthType.ONLINE) { + if (!session.isLoggedIn() && !session.isLoggingIn() && session.getRemoteAuthType() == AuthType.ONLINE) { // TODO it is safer to key authentication on something that won't change (UUID, not username) if (!couldLoginUserByName(session.getAuthData().getName())) { LoginEncryptionUtils.showLoginWindow(session); diff --git a/connector/src/main/java/org/geysermc/connector/network/remote/RemoteServer.java b/connector/src/main/java/org/geysermc/connector/network/remote/RemoteServer.java deleted file mode 100644 index b957b90d6..000000000 --- a/connector/src/main/java/org/geysermc/connector/network/remote/RemoteServer.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2019-2021 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.connector.network.remote; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class RemoteServer { - - private String address; - private int port; -} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index ee3f07f3e..d16cb1c2b 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -81,7 +81,6 @@ import org.geysermc.connector.entity.player.SessionPlayerEntity; import org.geysermc.connector.entity.player.SkullPlayerEntity; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.inventory.PlayerInventory; -import org.geysermc.connector.network.remote.RemoteServer; import org.geysermc.connector.network.session.auth.AuthData; import org.geysermc.connector.network.session.auth.BedrockClientData; import org.geysermc.connector.network.session.cache.*; @@ -113,13 +112,21 @@ public class GeyserSession implements CommandSender { private final GeyserConnector connector; private final UpstreamSession upstream; - private RemoteServer remoteServer; private Client downstream; @Setter private AuthData authData; @Setter private BedrockClientData clientData; + /* Setter for GeyserConnect */ + @Setter + private String remoteAddress; + @Setter + private int remotePort; + @Setter + private AuthType remoteAuthType; + /* Setter for GeyserConnect */ + @Deprecated @Setter private boolean microsoftAccount; @@ -438,9 +445,14 @@ public class GeyserSession implements CommandSender { }); } - public void connect(RemoteServer remoteServer) { + /** + * Send all necessary packets to load Bedrock into the server + */ + public void connect() { startGame(); - this.remoteServer = remoteServer; + this.remoteAddress = connector.getConfig().getRemote().getAddress(); + this.remotePort = connector.getConfig().getRemote().getPort(); + this.remoteAuthType = connector.getDefaultAuthType(); // Set the hardcoded shield ID to the ID we just defined in StartGamePacket upstream.getSession().getHardcodedBlockingId().set(ItemRegistry.SHIELD.getBedrockId()); @@ -485,8 +497,8 @@ public class GeyserSession implements CommandSender { } public void login() { - if (connector.getAuthType() != AuthType.ONLINE) { - if (connector.getAuthType() == AuthType.OFFLINE) { + if (this.remoteAuthType != AuthType.ONLINE) { + if (this.remoteAuthType == AuthType.OFFLINE) { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.offline")); } else { connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.auth.login.floodgate")); @@ -595,7 +607,7 @@ public class GeyserSession implements CommandSender { * After getting whatever credentials needed, we attempt to join the Java server. */ private void connectDownstream() { - boolean floodgate = connector.getAuthType() == AuthType.FLOODGATE; + boolean floodgate = this.remoteAuthType == AuthType.FLOODGATE; final PublicKey publicKey; if (floodgate) { @@ -618,7 +630,8 @@ public class GeyserSession implements CommandSender { // Start ticking tickThread = connector.getGeneralThreadPool().scheduleAtFixedRate(this::tick, 50, 50, TimeUnit.MILLISECONDS); - downstream = new Client(remoteServer.getAddress(), remoteServer.getPort(), protocol, new TcpSessionFactory()); + downstream = new Client(this.remoteAddress, this.remotePort, protocol, new TcpSessionFactory()); + disableSrvResolving(); if (connector.getConfig().getRemote().isUseProxyProtocol()) { downstream.getSession().setFlag(BuiltinFlags.ENABLE_CLIENT_PROXY_PROTOCOL, true); downstream.getSession().setFlag(BuiltinFlags.CLIENT_PROXIED_ADDRESS, upstream.getAddress()); @@ -666,7 +679,7 @@ public class GeyserSession implements CommandSender { disconnect(LanguageUtils.getPlayerLocaleString("geyser.network.remote.invalid_account", clientData.getLanguageCode())); return; } - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteServer.getAddress())); + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.connect", authData.getName(), protocol.getProfile().getName(), remoteAddress)); playerEntity.setUuid(protocol.getProfile().getId()); playerEntity.setUsername(protocol.getProfile().getName()); @@ -687,7 +700,7 @@ public class GeyserSession implements CommandSender { public void disconnected(DisconnectedEvent event) { loggingIn = false; loggedIn = false; - connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteServer.getAddress(), event.getReason())); + connector.getLogger().info(LanguageUtils.getLocaleStringLog("geyser.network.remote.disconnect", authData.getName(), remoteAddress, event.getReason())); if (event.getCause() != null) { event.getCause().printStackTrace(); } @@ -705,7 +718,7 @@ public class GeyserSession implements CommandSender { playerEntity.setUuid(profile.getId()); // Check if they are not using a linked account - if (connector.getAuthType() == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) { + if (remoteAuthType == AuthType.OFFLINE || playerEntity.getUuid().getMostSignificantBits() == 0) { SkinManager.handleBedrockSkin(playerEntity, clientData); } } @@ -793,6 +806,13 @@ public class GeyserSession implements CommandSender { collisionManager.updateScaffoldingFlags(); } + /** + * Will be overwritten for GeyserConnect. + */ + protected void disableSrvResolving() { + this.downstream.getSession().setFlag(BuiltinFlags.ATTEMPT_SRV_RESOLVE, false); + } + @Override public String getName() { return authData.getName(); diff --git a/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java b/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java index 5a0e41ed5..5af08292a 100644 --- a/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java +++ b/connector/src/main/java/org/geysermc/connector/skin/SkinManager.java @@ -286,7 +286,7 @@ public class SkinManager { String skinUrl = isAlex ? SkinProvider.EMPTY_SKIN_ALEX.getTextureUrl() : SkinProvider.EMPTY_SKIN.getTextureUrl(); String capeUrl = SkinProvider.EMPTY_CAPE.getTextureUrl(); - if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserConnector.getInstance().getAuthType() != AuthType.ONLINE) { + if (("steve".equals(skinUrl) || "alex".equals(skinUrl)) && GeyserConnector.getInstance().getDefaultAuthType() != AuthType.ONLINE) { GeyserSession session = GeyserConnector.getInstance().getPlayerByUuid(profile.getId()); if (session != null) { From f827b2885221cdb593331ac453f96863e1b56e2a Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Thu, 18 Mar 2021 00:53:14 -0400 Subject: [PATCH 154/161] More smoothed out shield code (#2051) - Shield code is now down as the player is sneaking - it doesn't wait for the server to update our sneaking status - Shield is now applied correctly when sneaking and then moving to the shield item --- .../org/geysermc/connector/entity/Entity.java | 33 +------------------ ...BedrockInventoryTransactionTranslator.java | 4 +-- .../BedrockMobEquipmentTranslator.java | 20 ++++++++--- .../player/BedrockActionTranslator.java | 33 ++++++++++++++++--- .../geysermc/connector/utils/BlockUtils.java | 10 +++--- 5 files changed, 54 insertions(+), 46 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/Entity.java b/connector/src/main/java/org/geysermc/connector/entity/Entity.java index 30e63f33c..2dcd49fb0 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/Entity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/Entity.java @@ -28,12 +28,6 @@ package org.geysermc.connector.entity; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Pose; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; -import com.github.steveice10.mc.protocol.data.game.entity.player.Hand; -import com.github.steveice10.mc.protocol.data.game.entity.player.PlayerAction; -import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerUseItemPacket; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.AttributeData; import com.nukkitx.protocol.bedrock.data.entity.EntityData; @@ -50,11 +44,9 @@ import org.geysermc.connector.entity.attribute.AttributeType; import org.geysermc.connector.entity.living.ArmorStandEntity; import org.geysermc.connector.entity.player.PlayerEntity; import org.geysermc.connector.entity.type.EntityType; -import org.geysermc.connector.inventory.PlayerInventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.utils.AttributeUtils; import org.geysermc.connector.network.translators.chat.MessageTranslator; +import org.geysermc.connector.utils.AttributeUtils; import java.util.ArrayList; import java.util.HashMap; @@ -278,29 +270,6 @@ public class Entity { if (!this.is(ArmorStandEntity.class)) { metadata.getFlags().setFlag(EntityFlag.INVISIBLE, (xd & 0x20) == 0x20); } - - // Shield code - if (session.getPlayerEntity().getEntityId() == entityId && metadata.getFlags().getFlag(EntityFlag.SNEAKING)) { - PlayerInventory playerInv = session.getPlayerInventory(); - if ((playerInv.getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) || - (playerInv.getOffhand().getJavaId() == ItemRegistry.SHIELD.getJavaId())) { - ClientPlayerUseItemPacket useItemPacket; - metadata.getFlags().setFlag(EntityFlag.BLOCKING, true); - if (playerInv.getItemInHand().getJavaId() == ItemRegistry.SHIELD.getJavaId()) { - useItemPacket = new ClientPlayerUseItemPacket(Hand.MAIN_HAND); - } - // Else we just assume it's the offhand, to simplify logic and to assure the packet gets sent - else { - useItemPacket = new ClientPlayerUseItemPacket(Hand.OFF_HAND); - } - session.sendDownstreamPacket(useItemPacket); - } - } else if (session.getPlayerEntity().getEntityId() == entityId && !metadata.getFlags().getFlag(EntityFlag.SNEAKING) && metadata.getFlags().getFlag(EntityFlag.BLOCKING)) { - metadata.getFlags().setFlag(EntityFlag.BLOCKING, false); - metadata.getFlags().setFlag(EntityFlag.IS_AVOIDING_BLOCK, true); - ClientPlayerActionPacket releaseItemPacket = new ClientPlayerActionPacket(PlayerAction.RELEASE_USE_ITEM, new Position(0, 0, 0), BlockFace.DOWN); - session.sendDownstreamPacket(releaseItemPacket); - } } break; case 1: // Air/bubbles diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java index 5258219ba..36c5be44f 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockInventoryTransactionTranslator.java @@ -94,7 +94,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator 1; ClientPlayerActionPacket dropAllPacket = new ClientPlayerActionPacket( dropAll ? PlayerAction.DROP_ITEM_STACK : PlayerAction.DROP_ITEM, - new Position(0, 0, 0), + BlockUtils.POSITION_ZERO, BlockFace.DOWN ); session.sendDownstreamPacket(dropAllPacket); @@ -292,7 +292,7 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator { @@ -53,6 +57,14 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator session.sendDownstreamPacket(new ClientPlayerUseItemPacket(Hand.MAIN_HAND)), + 50, TimeUnit.MILLISECONDS); + } + // Java sends a cooldown indicator whenever you switch an item CooldownUtils.sendCooldown(session); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java index ca4250bab..7751fb024 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/entity/player/BedrockActionTranslator.java @@ -28,15 +28,13 @@ package org.geysermc.connector.network.translators.bedrock.entity.player; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.player.*; import com.github.steveice10.mc.protocol.data.game.world.block.BlockFace; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerAbilitiesPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerActionPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerInteractEntityPacket; -import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerStatePacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.player.*; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.LevelEventType; import com.nukkitx.protocol.bedrock.data.PlayerActionType; import com.nukkitx.protocol.bedrock.data.entity.EntityEventType; +import com.nukkitx.protocol.bedrock.data.entity.EntityFlag; import com.nukkitx.protocol.bedrock.packet.EntityEventPacket; import com.nukkitx.protocol.bedrock.packet.LevelEventPacket; import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; @@ -49,6 +47,7 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.BlockUtils; @@ -101,11 +100,37 @@ public class BedrockActionTranslator extends PacketTranslator Date: Thu, 18 Mar 2021 01:11:21 -0400 Subject: [PATCH 155/161] Update the interactive tag when necessary (#2039) Update the interactive tag on entity metadata update, item slot change, and sneaking update; that way, if the tag should change, it will change. --- .../network/session/GeyserSession.java | 11 + .../BedrockMobEquipmentTranslator.java | 6 + .../player/BedrockInteractTranslator.java | 340 +--------------- .../entity/JavaEntityMetadataTranslator.java | 6 + .../utils/InteractiveTagManager.java | 376 ++++++++++++++++++ 5 files changed, 406 insertions(+), 333 deletions(-) create mode 100644 connector/src/main/java/org/geysermc/connector/utils/InteractiveTagManager.java diff --git a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java index d16cb1c2b..c7f13398e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/GeyserSession.java @@ -264,6 +264,12 @@ public class GeyserSession implements CommandSender { @Setter private Entity ridingVehicleEntity; + /** + * The entity that the client is currently looking at. + */ + @Setter + private Entity mouseoverEntity; + @Setter private Int2ObjectMap craftingRecipes; private final Set unlockedRecipes; @@ -804,6 +810,11 @@ public class GeyserSession implements CommandSender { this.sneaking = sneaking; collisionManager.updatePlayerBoundingBox(); collisionManager.updateScaffoldingFlags(); + + if (mouseoverEntity != null) { + // Horses, etc can change their property depending on if you're sneaking + InteractiveTagManager.updateTag(this, mouseoverEntity); + } } /** diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java index 134b3dab0..e07f0ae1e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockMobEquipmentTranslator.java @@ -35,6 +35,7 @@ import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.network.translators.item.ItemRegistry; import org.geysermc.connector.utils.CooldownUtils; +import org.geysermc.connector.utils.InteractiveTagManager; import java.util.concurrent.TimeUnit; @@ -67,5 +68,10 @@ public class BedrockMobEquipmentTranslator extends PacketTranslator { - /** - * A list of all foods a horse/donkey can eat on Java Edition. - * Used to display interactive tag if needed. - */ - private static final List DONKEY_AND_HORSE_FOODS = Arrays.asList("golden_apple", "enchanted_golden_apple", - "golden_carrot", "sugar", "apple", "wheat", "hay_block"); - - /** - * A list of all flowers. Used for feeding bees. - */ - private static final List FLOWERS = Arrays.asList("dandelion", "poppy", "blue_orchid", "allium", "azure_bluet", - "red_tulip", "pink_tulip", "white_tulip", "orange_tulip", "cornflower", "lily_of_the_valley", "wither_rose", - "sunflower", "lilac", "rose_bush", "peony"); - - /** - * All entity types that can be leashed on Java Edition - */ - private static final List LEASHABLE_MOB_TYPES = Arrays.asList(EntityType.BEE, EntityType.CAT, EntityType.CHICKEN, - EntityType.COW, EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX, EntityType.HOGLIN, EntityType.HORSE, EntityType.SKELETON_HORSE, - EntityType.ZOMBIE_HORSE, EntityType.IRON_GOLEM, EntityType.LLAMA, EntityType.TRADER_LLAMA, EntityType.MOOSHROOM, - EntityType.MULE, EntityType.OCELOT, EntityType.PARROT, EntityType.PIG, EntityType.POLAR_BEAR, EntityType.RABBIT, - EntityType.SHEEP, EntityType.SNOW_GOLEM, EntityType.STRIDER, EntityType.WOLF, EntityType.ZOGLIN); - - private static final List SADDLEABLE_WHEN_TAMED_MOB_TYPES = Arrays.asList(EntityType.DONKEY, EntityType.HORSE, - EntityType.ZOMBIE_HORSE, EntityType.MULE); - /** - * A list of all foods a wolf can eat on Java Edition. - * Used to display interactive tag if needed. - */ - private static final List WOLF_FOODS = Arrays.asList("pufferfish", "tropical_fish", "chicken", "cooked_chicken", - "porkchop", "beef", "rabbit", "cooked_porkchop", "cooked_beef", "rotten_flesh", "mutton", "cooked_mutton", - "cooked_rabbit"); - @Override public void translate(InteractPacket packet, GeyserSession session) { Entity entity; @@ -121,241 +81,16 @@ public class BedrockInteractTranslator extends PacketTranslator // Handle the buttons for mobile - "Mount", etc; and the suggestions for console - "ZL: Mount", etc if (packet.getRuntimeEntityId() != 0) { Entity interactEntity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); - if (interactEntity == null) + session.setMouseoverEntity(interactEntity); + if (interactEntity == null) { return; - EntityDataMap entityMetadata = interactEntity.getMetadata(); - ItemEntry itemEntry = session.getPlayerInventory().getItemInHand().getItemEntry(); - String javaIdentifierStripped = itemEntry.getJavaIdentifier().replace("minecraft:", ""); - - // TODO - in the future, update these in the metadata? So the client doesn't have to wiggle their cursor around for it to happen - // TODO - also, might be good to abstract out the eating thing. I know there will need to be food tracked for https://github.com/GeyserMC/Geyser/issues/1005 but not all food is breeding food - InteractiveTag interactiveTag = InteractiveTag.NONE; - if (entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == session.getPlayerEntity().getGeyserId()) { - // Unleash the entity - interactiveTag = InteractiveTag.REMOVE_LEASH; - } else if (javaIdentifierStripped.equals("saddle") && !entityMetadata.getFlags().getFlag(EntityFlag.SADDLED) && - ((SADDLEABLE_WHEN_TAMED_MOB_TYPES.contains(interactEntity.getEntityType()) && entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) || - interactEntity.getEntityType() == EntityType.PIG || interactEntity.getEntityType() == EntityType.STRIDER)) { - // Entity can be saddled and the conditions meet (entity can be saddled and, if needed, is tamed) - interactiveTag = InteractiveTag.SADDLE; - } else if (javaIdentifierStripped.equals("name_tag") && session.getPlayerInventory().getItemInHand().getNbt() != null && - session.getPlayerInventory().getItemInHand().getNbt().contains("display")) { - // Holding a named name tag - interactiveTag = InteractiveTag.NAME; - } else if (javaIdentifierStripped.equals("lead") && LEASHABLE_MOB_TYPES.contains(interactEntity.getEntityType()) && - entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == -1L) { - // Holding a leash and the mob is leashable for sure - // (Plugins can change this behavior so that's something to look into in the far far future) - interactiveTag = InteractiveTag.LEASH; - } else { - switch (interactEntity.getEntityType()) { - case BEE: - if (FLOWERS.contains(javaIdentifierStripped)) { - interactiveTag = InteractiveTag.FEED; - } - break; - case BOAT: - interactiveTag = InteractiveTag.BOARD_BOAT; - break; - case CAT: - if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { - interactiveTag = InteractiveTag.FEED; - } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && - entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { - // Tamed and owned by player - can sit/stand - interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; - break; - } - break; - case CHICKEN: - if (javaIdentifierStripped.contains("seeds")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case MOOSHROOM: - // Shear the mooshroom - if (javaIdentifierStripped.equals("shears")) { - interactiveTag = InteractiveTag.MOOSHROOM_SHEAR; - break; - } - // Bowls are acceptable here - else if (javaIdentifierStripped.equals("bowl")) { - interactiveTag = InteractiveTag.MOOSHROOM_MILK_STEW; - break; - } - // Fall down to COW as this works on mooshrooms - case COW: - if (javaIdentifierStripped.equals("wheat")) { - interactiveTag = InteractiveTag.FEED; - } else if (javaIdentifierStripped.equals("bucket")) { - // Milk the cow - interactiveTag = InteractiveTag.MILK; - } - break; - case CREEPER: - if (javaIdentifierStripped.equals("flint_and_steel")) { - // Today I learned that you can ignite a creeper with flint and steel! Huh. - interactiveTag = InteractiveTag.IGNITE_CREEPER; - } - break; - case DONKEY: - case LLAMA: - case MULE: - if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && !entityMetadata.getFlags().getFlag(EntityFlag.CHESTED) - && javaIdentifierStripped.equals("chest")) { - // Can attach a chest - interactiveTag = InteractiveTag.ATTACH_CHEST; - break; - } - // Intentional fall-through - case HORSE: - case SKELETON_HORSE: - case TRADER_LLAMA: - case ZOMBIE_HORSE: - boolean tamed = entityMetadata.getFlags().getFlag(EntityFlag.TAMED); - if (session.isSneaking() && tamed && (interactEntity instanceof HorseEntity || entityMetadata.getFlags().getFlag(EntityFlag.CHESTED))) { - interactiveTag = InteractiveTag.OPEN_CONTAINER; - break; - } - // have another switch statement as, while these share mount attributes they don't share food - switch (interactEntity.getEntityType()) { - case LLAMA: - case TRADER_LLAMA: - if (javaIdentifierStripped.equals("wheat") || javaIdentifierStripped.equals("hay_block")) { - interactiveTag = InteractiveTag.FEED; - break; - } - case DONKEY: - case HORSE: - // Undead can't eat - if (DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped)) { - interactiveTag = InteractiveTag.FEED; - break; - } - } - if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY)) { - // Can't ride a baby - if (tamed) { - interactiveTag = InteractiveTag.RIDE_HORSE; - } else if (itemEntry.equals(ItemEntry.AIR)) { - // Can't hide an untamed entity without having your hand empty - interactiveTag = InteractiveTag.MOUNT; - } - } - break; - case FOX: - if (javaIdentifierStripped.equals("sweet_berries")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case HOGLIN: - if (javaIdentifierStripped.equals("crimson_fungus")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case MINECART: - interactiveTag = InteractiveTag.RIDE_MINECART; - break; - case MINECART_CHEST: - case MINECART_COMMAND_BLOCK: - case MINECART_HOPPER: - interactiveTag = InteractiveTag.OPEN_CONTAINER; - break; - case OCELOT: - if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case PANDA: - if (javaIdentifierStripped.equals("bamboo")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case PARROT: - if (javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case PIG: - if (javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot")) { - interactiveTag = InteractiveTag.FEED; - } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { - interactiveTag = InteractiveTag.MOUNT; - } - break; - case PIGLIN: - if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY) && javaIdentifierStripped.equals("gold_ingot")) { - interactiveTag = InteractiveTag.BARTER; - } - break; - case RABBIT: - if (javaIdentifierStripped.equals("dandelion") || javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("golden_carrot")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case SHEEP: - if (javaIdentifierStripped.equals("wheat")) { - interactiveTag = InteractiveTag.FEED; - } else if (!entityMetadata.getFlags().getFlag(EntityFlag.SHEARED)) { - if (javaIdentifierStripped.equals("shears")) { - // Shear the sheep - interactiveTag = InteractiveTag.SHEAR; - } else if (javaIdentifierStripped.contains("_dye")) { - // Dye the sheep - interactiveTag = InteractiveTag.DYE; - } - } - break; - case STRIDER: - if (javaIdentifierStripped.equals("warped_fungus")) { - interactiveTag = InteractiveTag.FEED; - } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { - interactiveTag = InteractiveTag.RIDE_STRIDER; - } - break; - case TURTLE: - if (javaIdentifierStripped.equals("seagrass")) { - interactiveTag = InteractiveTag.FEED; - } - break; - case VILLAGER: - if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0 - && entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby - interactiveTag = InteractiveTag.TRADE; - } - break; - case WANDERING_TRADER: - interactiveTag = InteractiveTag.TRADE; // Since you can always trade with a wandering villager, presumably. - break; - case WOLF: - if (javaIdentifierStripped.equals("bone") && !entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { - // Bone and untamed - can tame - interactiveTag = InteractiveTag.TAME; - } else if (WOLF_FOODS.contains(javaIdentifierStripped)) { - // Compatible food in hand - feed - // Sometimes just sits/stands when the wolf isn't hungry - there doesn't appear to be a way to fix this - interactiveTag = InteractiveTag.FEED; - } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && - entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { - // Tamed and owned by player - can sit/stand - interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; - } - break; - case ZOMBIE_VILLAGER: - // We can't guarantee the existence of the weakness effect so we just always show it. - if (javaIdentifierStripped.equals("golden_apple")) { - interactiveTag = InteractiveTag.CURE; - } - break; - default: - break; - } } - session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag.getValue()); - session.getPlayerEntity().updateBedrockMetadata(session); + + InteractiveTagManager.updateTag(session, interactEntity); } else { - if (!session.getPlayerEntity().getMetadata().getString(EntityData.INTERACTIVE_TAG).isEmpty()) { + if (session.getMouseoverEntity() != null) { // No interactive tag should be sent + session.setMouseoverEntity(null); session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, ""); session.getPlayerEntity().updateBedrockMetadata(session); } @@ -384,65 +119,4 @@ public class BedrockInteractTranslator extends PacketTranslator break; } } - - /** - * All interactive tags in enum form. For potential API usage. - */ - public enum InteractiveTag { - NONE(true), - IGNITE_CREEPER("creeper"), - EDIT, - LEAVE_BOAT("exit.boat"), - FEED, - FISH("fishing"), - MILK, - MOOSHROOM_SHEAR("mooshear"), - MOOSHROOM_MILK_STEW("moostew"), - BOARD_BOAT("ride.boat"), - RIDE_MINECART("ride.minecart"), - RIDE_HORSE("ride.horse"), - RIDE_STRIDER("ride.strider"), - SHEAR, - SIT, - STAND, - TALK, - TAME, - DYE, - CURE, - OPEN_CONTAINER("opencontainer"), - CREATE_MAP("createMap"), - TAKE_PICTURE("takepicture"), - SADDLE, - MOUNT, - BOOST, - WRITE, - LEASH, - REMOVE_LEASH("unleash"), - NAME, - ATTACH_CHEST("attachchest"), - TRADE, - POSE_ARMOR_STAND("armorstand.pose"), - EQUIP_ARMOR_STAND("armorstand.equip"), - READ, - WAKE_VILLAGER("wakevillager"), - BARTER; - - /** - * The full string that should be passed on to the client. - */ - @Getter - private final String value; - - InteractiveTag(boolean isNone) { - this.value = ""; - } - - InteractiveTag(String value) { - this.value = "action.interact." + value; - } - - InteractiveTag() { - this.value = "action.interact." + name().toLowerCase(); - } - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityMetadataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityMetadataTranslator.java index 3c4153df3..73047d0c4 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityMetadataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityMetadataTranslator.java @@ -32,6 +32,7 @@ import org.geysermc.connector.network.translators.Translator; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; import com.github.steveice10.mc.protocol.packet.ingame.server.entity.ServerEntityMetadataPacket; +import org.geysermc.connector.utils.InteractiveTagManager; import org.geysermc.connector.utils.LanguageUtils; @Translator(packet = ServerEntityMetadataPacket.class) @@ -63,5 +64,10 @@ public class JavaEntityMetadataTranslator extends PacketTranslator DONKEY_AND_HORSE_FOODS = ImmutableSet.of("golden_apple", "enchanted_golden_apple", + "golden_carrot", "sugar", "apple", "wheat", "hay_block"); + + /** + * A list of all flowers. Used for feeding bees. + */ + private static final Set FLOWERS = ImmutableSet.of("dandelion", "poppy", "blue_orchid", "allium", "azure_bluet", + "red_tulip", "pink_tulip", "white_tulip", "orange_tulip", "cornflower", "lily_of_the_valley", "wither_rose", + "sunflower", "lilac", "rose_bush", "peony"); + + /** + * All entity types that can be leashed on Java Edition + */ + private static final Set LEASHABLE_MOB_TYPES = EnumSet.of(EntityType.BEE, EntityType.CAT, EntityType.CHICKEN, + EntityType.COW, EntityType.DOLPHIN, EntityType.DONKEY, EntityType.FOX, EntityType.HOGLIN, EntityType.HORSE, EntityType.SKELETON_HORSE, + EntityType.ZOMBIE_HORSE, EntityType.IRON_GOLEM, EntityType.LLAMA, EntityType.TRADER_LLAMA, EntityType.MOOSHROOM, + EntityType.MULE, EntityType.OCELOT, EntityType.PARROT, EntityType.PIG, EntityType.POLAR_BEAR, EntityType.RABBIT, + EntityType.SHEEP, EntityType.SNOW_GOLEM, EntityType.STRIDER, EntityType.WOLF, EntityType.ZOGLIN); + + private static final Set SADDLEABLE_WHEN_TAMED_MOB_TYPES = EnumSet.of(EntityType.DONKEY, EntityType.HORSE, + EntityType.ZOMBIE_HORSE, EntityType.MULE); + + /** + * A list of all foods a wolf can eat on Java Edition. + * Used to display interactive tag if needed. + */ + private static final Set WOLF_FOODS = ImmutableSet.of("pufferfish", "tropical_fish", "chicken", "cooked_chicken", + "porkchop", "beef", "rabbit", "cooked_porkchop", "cooked_beef", "rotten_flesh", "mutton", "cooked_mutton", + "cooked_rabbit"); + + /** + * Update the suggestion that the client currently has on their screen for this entity (for example, "Feed" or "Ride") + * + * @param session the Bedrock client session + * @param interactEntity the entity that the client is currently facing. + */ + public static void updateTag(GeyserSession session, Entity interactEntity) { + EntityDataMap entityMetadata = interactEntity.getMetadata(); + ItemEntry itemEntry = session.getPlayerInventory().getItemInHand().getItemEntry(); + String javaIdentifierStripped = itemEntry.getJavaIdentifier().replace("minecraft:", ""); + + // TODO - in the future, update these in the metadata? So the client doesn't have to wiggle their cursor around for it to happen + // TODO - also, might be good to abstract out the eating thing. I know there will need to be food tracked for https://github.com/GeyserMC/Geyser/issues/1005 but not all food is breeding food + InteractiveTag interactiveTag = InteractiveTag.NONE; + if (entityMetadata.getLong(EntityData.LEASH_HOLDER_EID) == session.getPlayerEntity().getGeyserId()) { + // Unleash the entity + interactiveTag = InteractiveTag.REMOVE_LEASH; + } else if (javaIdentifierStripped.equals("saddle") && !entityMetadata.getFlags().getFlag(EntityFlag.SADDLED) && + ((SADDLEABLE_WHEN_TAMED_MOB_TYPES.contains(interactEntity.getEntityType()) && entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && !session.isSneaking()) || + interactEntity.getEntityType() == EntityType.PIG || interactEntity.getEntityType() == EntityType.STRIDER)) { + // Entity can be saddled and the conditions meet (entity can be saddled and, if needed, is tamed) + interactiveTag = InteractiveTag.SADDLE; + } else if (javaIdentifierStripped.equals("name_tag") && session.getPlayerInventory().getItemInHand().getNbt() != null && + session.getPlayerInventory().getItemInHand().getNbt().contains("display")) { + // Holding a named name tag + interactiveTag = InteractiveTag.NAME; + } else if (javaIdentifierStripped.equals("lead") && LEASHABLE_MOB_TYPES.contains(interactEntity.getEntityType()) && + entityMetadata.getLong(EntityData.LEASH_HOLDER_EID, -1L) == -1L) { + // Holding a leash and the mob is leashable for sure + // (Plugins can change this behavior so that's something to look into in the far far future) + interactiveTag = InteractiveTag.LEASH; + } else { + switch (interactEntity.getEntityType()) { + case BEE: + if (FLOWERS.contains(javaIdentifierStripped)) { + interactiveTag = InteractiveTag.FEED; + } + break; + case BOAT: + interactiveTag = InteractiveTag.BOARD_BOAT; + break; + case CAT: + if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && + entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { + // Tamed and owned by player - can sit/stand + interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + break; + } + break; + case CHICKEN: + if (javaIdentifierStripped.contains("seeds")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case MOOSHROOM: + // Shear the mooshroom + if (javaIdentifierStripped.equals("shears")) { + interactiveTag = InteractiveTag.MOOSHROOM_SHEAR; + break; + } + // Bowls are acceptable here + else if (javaIdentifierStripped.equals("bowl")) { + interactiveTag = InteractiveTag.MOOSHROOM_MILK_STEW; + break; + } + // Fall down to COW as this works on mooshrooms + case COW: + if (javaIdentifierStripped.equals("wheat")) { + interactiveTag = InteractiveTag.FEED; + } else if (javaIdentifierStripped.equals("bucket")) { + // Milk the cow + interactiveTag = InteractiveTag.MILK; + } + break; + case CREEPER: + if (javaIdentifierStripped.equals("flint_and_steel")) { + // Today I learned that you can ignite a creeper with flint and steel! Huh. + interactiveTag = InteractiveTag.IGNITE_CREEPER; + } + break; + case DONKEY: + case LLAMA: + case MULE: + if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && !entityMetadata.getFlags().getFlag(EntityFlag.CHESTED) + && javaIdentifierStripped.equals("chest")) { + // Can attach a chest + interactiveTag = InteractiveTag.ATTACH_CHEST; + break; + } + // Intentional fall-through + case HORSE: + case SKELETON_HORSE: + case TRADER_LLAMA: + case ZOMBIE_HORSE: + boolean tamed = entityMetadata.getFlags().getFlag(EntityFlag.TAMED); + if (session.isSneaking() && tamed && (interactEntity instanceof HorseEntity || entityMetadata.getFlags().getFlag(EntityFlag.CHESTED))) { + interactiveTag = InteractiveTag.OPEN_CONTAINER; + break; + } + // have another switch statement as, while these share mount attributes they don't share food + switch (interactEntity.getEntityType()) { + case LLAMA: + case TRADER_LLAMA: + if (javaIdentifierStripped.equals("wheat") || javaIdentifierStripped.equals("hay_block")) { + interactiveTag = InteractiveTag.FEED; + break; + } + case DONKEY: + case HORSE: + // Undead can't eat + if (DONKEY_AND_HORSE_FOODS.contains(javaIdentifierStripped)) { + interactiveTag = InteractiveTag.FEED; + break; + } + } + if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY)) { + // Can't ride a baby + if (tamed) { + interactiveTag = InteractiveTag.RIDE_HORSE; + } else if (itemEntry.equals(ItemEntry.AIR)) { + // Can't hide an untamed entity without having your hand empty + interactiveTag = InteractiveTag.MOUNT; + } + } + break; + case FOX: + if (javaIdentifierStripped.equals("sweet_berries")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case HOGLIN: + if (javaIdentifierStripped.equals("crimson_fungus")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case MINECART: + interactiveTag = InteractiveTag.RIDE_MINECART; + break; + case MINECART_CHEST: + case MINECART_COMMAND_BLOCK: + case MINECART_HOPPER: + interactiveTag = InteractiveTag.OPEN_CONTAINER; + break; + case OCELOT: + if (javaIdentifierStripped.equals("cod") || javaIdentifierStripped.equals("salmon")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case PANDA: + if (javaIdentifierStripped.equals("bamboo")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case PARROT: + if (javaIdentifierStripped.contains("seeds") || javaIdentifierStripped.equals("cookie")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case PIG: + if (javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("potato") || javaIdentifierStripped.equals("beetroot")) { + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { + interactiveTag = InteractiveTag.MOUNT; + } + break; + case PIGLIN: + if (!entityMetadata.getFlags().getFlag(EntityFlag.BABY) && javaIdentifierStripped.equals("gold_ingot")) { + interactiveTag = InteractiveTag.BARTER; + } + break; + case RABBIT: + if (javaIdentifierStripped.equals("dandelion") || javaIdentifierStripped.equals("carrot") || javaIdentifierStripped.equals("golden_carrot")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case SHEEP: + if (javaIdentifierStripped.equals("wheat")) { + interactiveTag = InteractiveTag.FEED; + } else if (!entityMetadata.getFlags().getFlag(EntityFlag.SHEARED)) { + if (javaIdentifierStripped.equals("shears")) { + // Shear the sheep + interactiveTag = InteractiveTag.SHEAR; + } else if (javaIdentifierStripped.contains("_dye")) { + // Dye the sheep + interactiveTag = InteractiveTag.DYE; + } + } + break; + case STRIDER: + if (javaIdentifierStripped.equals("warped_fungus")) { + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.SADDLED)) { + interactiveTag = InteractiveTag.RIDE_STRIDER; + } + break; + case TURTLE: + if (javaIdentifierStripped.equals("seagrass")) { + interactiveTag = InteractiveTag.FEED; + } + break; + case VILLAGER: + if (entityMetadata.getInt(EntityData.VARIANT) != 14 && entityMetadata.getInt(EntityData.VARIANT) != 0 + && entityMetadata.getFloat(EntityData.SCALE) >= 0.75f) { // Not a nitwit, has a profession and is not a baby + interactiveTag = InteractiveTag.TRADE; + } + break; + case WANDERING_TRADER: + interactiveTag = InteractiveTag.TRADE; // Since you can always trade with a wandering villager, presumably. + break; + case WOLF: + if (javaIdentifierStripped.equals("bone") && !entityMetadata.getFlags().getFlag(EntityFlag.TAMED)) { + // Bone and untamed - can tame + interactiveTag = InteractiveTag.TAME; + } else if (WOLF_FOODS.contains(javaIdentifierStripped)) { + // Compatible food in hand - feed + // Sometimes just sits/stands when the wolf isn't hungry - there doesn't appear to be a way to fix this + interactiveTag = InteractiveTag.FEED; + } else if (entityMetadata.getFlags().getFlag(EntityFlag.TAMED) && + entityMetadata.getLong(EntityData.OWNER_EID) == session.getPlayerEntity().getGeyserId()) { + // Tamed and owned by player - can sit/stand + interactiveTag = entityMetadata.getFlags().getFlag(EntityFlag.SITTING) ? InteractiveTag.STAND : InteractiveTag.SIT; + } + break; + case ZOMBIE_VILLAGER: + // We can't guarantee the existence of the weakness effect so we just always show it. + if (javaIdentifierStripped.equals("golden_apple")) { + interactiveTag = InteractiveTag.CURE; + } + break; + default: + break; + } + } + session.getPlayerEntity().getMetadata().put(EntityData.INTERACTIVE_TAG, interactiveTag.getValue()); + session.getPlayerEntity().updateBedrockMetadata(session); + } + + /** + * All interactive tags in enum form. For potential API usage. + */ + public enum InteractiveTag { + NONE(true), + IGNITE_CREEPER("creeper"), + EDIT, + LEAVE_BOAT("exit.boat"), + FEED, + FISH("fishing"), + MILK, + MOOSHROOM_SHEAR("mooshear"), + MOOSHROOM_MILK_STEW("moostew"), + BOARD_BOAT("ride.boat"), + RIDE_MINECART("ride.minecart"), + RIDE_HORSE("ride.horse"), + RIDE_STRIDER("ride.strider"), + SHEAR, + SIT, + STAND, + TALK, + TAME, + DYE, + CURE, + OPEN_CONTAINER("opencontainer"), + CREATE_MAP("createMap"), + TAKE_PICTURE("takepicture"), + SADDLE, + MOUNT, + BOOST, + WRITE, + LEASH, + REMOVE_LEASH("unleash"), + NAME, + ATTACH_CHEST("attachchest"), + TRADE, + POSE_ARMOR_STAND("armorstand.pose"), + EQUIP_ARMOR_STAND("armorstand.equip"), + READ, + WAKE_VILLAGER("wakevillager"), + BARTER; + + /** + * The full string that should be passed on to the client. + */ + @Getter + private final String value; + + InteractiveTag(boolean isNone) { + this.value = ""; + } + + InteractiveTag(String value) { + this.value = "action.interact." + value; + } + + InteractiveTag() { + this.value = "action.interact." + name().toLowerCase(); + } + } +} From 10c77a3214ffb03c93a5b4401c89be36150b3c8d Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 21 Mar 2021 11:28:19 -0400 Subject: [PATCH 156/161] Fix sign color changes messing with character count (#2054) Color changes do not affect the maximum amount of characters allowed on a line. --- .../entity/SignBlockEntityTranslator.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java index a92fac599..61ca4fa9c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/world/block/entity/SignBlockEntityTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.connector.network.translators.world.block.entity; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.nbt.NbtMapBuilder; import org.geysermc.connector.network.translators.chat.MessageTranslator; import org.geysermc.connector.utils.SignUtils; @@ -35,7 +36,7 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { /** * Maps a color stored in a sign's Color tag to a Bedrock Edition formatting code. *
- * The color names correspond to dye names, because of this we can't use {@link MessageTranslator#getColor(String)}. + * The color names correspond to dye names, because of this we can't use a more global method. * * @param javaColor The dye color stored in the sign's Color tag. * @return A Bedrock Edition formatting code for valid dye colors, otherwise an empty string. @@ -101,27 +102,34 @@ public class SignBlockEntityTranslator extends BlockEntityTranslator { String signLine = getOrDefault(tag.getValue().get("Text" + currentLine), ""); signLine = MessageTranslator.convertMessageLenient(signLine); - // Trim any trailing formatting codes - if (signLine.length() > 2 && signLine.toCharArray()[signLine.length() - 2] == '\u00a7') { - signLine = signLine.substring(0, signLine.length() - 2); - } - // Check the character width on the sign to ensure there is no overflow that is usually hidden // to Java Edition clients but will appear to Bedrock clients int signWidth = 0; StringBuilder finalSignLine = new StringBuilder(); + boolean previousCharacterWasFormatting = false; // Color changes do not count for maximum width for (char c : signLine.toCharArray()) { - signWidth += SignUtils.getCharacterWidth(c); + if (c == '\u00a7') { + // Don't count this character + previousCharacterWasFormatting = true; + } else if (previousCharacterWasFormatting) { + // Don't count this character either + previousCharacterWasFormatting = false; + } else { + signWidth += SignUtils.getCharacterWidth(c); + } + if (signWidth <= SignUtils.BEDROCK_CHARACTER_WIDTH_MAX) { finalSignLine.append(c); } else { + // Adding the character would make Bedrock move to the next line - Java doesn't do that, so we do not want to break; } } // Java Edition 1.14 added the ability to change the text color of the whole sign using dye - if (tag.contains("Color")) { - signText.append(getBedrockSignColor(tag.get("Color").getValue().toString())); + Tag color = tag.get("Color"); + if (color != null) { + signText.append(getBedrockSignColor(color.getValue().toString())); } signText.append(finalSignLine.toString()); From 86397ef4836e2e716f910d210a1e5ebd8fdeec57 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Sun, 21 Mar 2021 11:31:09 -0400 Subject: [PATCH 157/161] Chunk cache improvements (#2053) - Check for minimum and maximum Y values to fix stack traces - Don't run half of the villager sleeping code unless they're actually sleeping --- .../connector/entity/FishingHookEntity.java | 42 ++++++------ .../living/merchant/VillagerEntity.java | 65 +++++++++---------- .../network/session/cache/ChunkCache.java | 11 ++++ 3 files changed, 62 insertions(+), 56 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java index 06e56ad03..0738c3819 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/FishingHookEntity.java @@ -96,27 +96,25 @@ public class FishingHookEntity extends ThrowableEntity { boolean touchingWater = false; boolean collided = false; for (Vector3i blockPos : collidableBlocks) { - if (0 <= blockPos.getY() && blockPos.getY() <= 255) { - int blockID = session.getConnector().getWorldManager().getBlockAt(session, blockPos); - BlockCollision blockCollision = CollisionTranslator.getCollision(blockID, blockPos.getX(), blockPos.getY(), blockPos.getZ()); - if (blockCollision != null && blockCollision.checkIntersection(boundingBox)) { - // TODO Push bounding box out of collision to improve movement - collided = true; - } + int blockID = session.getConnector().getWorldManager().getBlockAt(session, blockPos); + BlockCollision blockCollision = CollisionTranslator.getCollision(blockID, blockPos.getX(), blockPos.getY(), blockPos.getZ()); + if (blockCollision != null && blockCollision.checkIntersection(boundingBox)) { + // TODO Push bounding box out of collision to improve movement + collided = true; + } - int waterLevel = BlockStateValues.getWaterLevel(blockID); - if (BlockTranslator.isWaterlogged(blockID)) { - waterLevel = 0; + int waterLevel = BlockStateValues.getWaterLevel(blockID); + if (BlockTranslator.isWaterlogged(blockID)) { + waterLevel = 0; + } + if (waterLevel >= 0) { + double waterMaxY = blockPos.getY() + 1 - (waterLevel + 1) / 9.0; + // Falling water is a full block + if (waterLevel >= 8) { + waterMaxY = blockPos.getY() + 1; } - if (waterLevel >= 0) { - double waterMaxY = blockPos.getY() + 1 - (waterLevel + 1) / 9.0; - // Falling water is a full block - if (waterLevel >= 8) { - waterMaxY = blockPos.getY() + 1; - } - if (position.getY() <= waterMaxY) { - touchingWater = true; - } + if (position.getY() <= waterMaxY) { + touchingWater = true; } } } @@ -177,10 +175,8 @@ public class FishingHookEntity extends ThrowableEntity { */ protected boolean isInAir(GeyserSession session) { if (session.getConnector().getConfig().isCacheChunks()) { - if (0 <= position.getFloorY() && position.getFloorY() <= 255) { - int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); - return block == BlockTranslator.JAVA_AIR_ID; - } + int block = session.getConnector().getWorldManager().getBlockAt(session, position.toInt()); + return block == BlockTranslator.JAVA_AIR_ID; } return false; } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java index 56354774d..fa5785fe5 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/merchant/VillagerEntity.java @@ -26,7 +26,6 @@ package org.geysermc.connector.entity.living.merchant; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.entity.metadata.VillagerData; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; @@ -101,11 +100,17 @@ public class VillagerEntity extends AbstractMerchantEntity { @Override public void moveRelative(GeyserSession session, double relX, double relY, double relZ, Vector3f rotation, boolean isOnGround) { + if (!metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { + // No need to worry about extra processing to compensate for sleeping + super.moveRelative(session, relX, relY, relZ, rotation, isOnGround); + return; + } + int z = 0; int bedId = 0; float bedPositionSubtractorW = 0; float bedPositionSubtractorN = 0; - Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION); + Vector3i bedPosition = metadata.getPos(EntityData.BED_POSITION, null); if (session.getConnector().getConfig().isCacheChunks() && bedPosition != null) { bedId = session.getConnector().getWorldManager().getBlockAt(session, bedPosition); } @@ -117,39 +122,33 @@ public class VillagerEntity extends AbstractMerchantEntity { MoveEntityAbsolutePacket moveEntityPacket = new MoveEntityAbsolutePacket(); moveEntityPacket.setRuntimeEntityId(geyserId); //Sets Villager position and rotation when sleeping - if (!metadata.getFlags().getFlag(EntityFlag.SLEEPING)) { - moveEntityPacket.setPosition(position); - moveEntityPacket.setRotation(getBedrockRotation()); - } else { - //String Setup - Pattern r = Pattern.compile("facing=([a-z]+)"); - Matcher m = r.matcher(bedRotationZ); - if (m.find()) { - switch (m.group(0)) { - case "facing=south": - //bed is facing south - z = 180; - bedPositionSubtractorW = -.5f; - break; - case "facing=east": - //bed is facing east - z = 90; - bedPositionSubtractorW = -.5f; - break; - case "facing=west": - //bed is facing west - z = 270; - bedPositionSubtractorW = .5f; - break; - case "facing=north": - //rotation does not change because north is 0 - bedPositionSubtractorN = .5f; - break; - } + Pattern r = Pattern.compile("facing=([a-z]+)"); + Matcher m = r.matcher(bedRotationZ); + if (m.find()) { + switch (m.group(0)) { + case "facing=south": + //bed is facing south + z = 180; + bedPositionSubtractorW = -.5f; + break; + case "facing=east": + //bed is facing east + z = 90; + bedPositionSubtractorW = -.5f; + break; + case "facing=west": + //bed is facing west + z = 270; + bedPositionSubtractorW = .5f; + break; + case "facing=north": + //rotation does not change because north is 0 + bedPositionSubtractorN = .5f; + break; } - moveEntityPacket.setRotation(Vector3f.from(0, 0, z)); - moveEntityPacket.setPosition(Vector3f.from(position.getX() + bedPositionSubtractorW, position.getY(), position.getZ() + bedPositionSubtractorN)); } + moveEntityPacket.setRotation(Vector3f.from(0, 0, z)); + moveEntityPacket.setPosition(Vector3f.from(position.getX() + bedPositionSubtractorW, position.getY(), position.getZ() + bedPositionSubtractorN)); moveEntityPacket.setOnGround(isOnGround); moveEntityPacket.setTeleported(false); session.sendUpstreamPacket(moveEntityPacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java index bf1f4718e..d182a6f12 100644 --- a/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/ChunkCache.java @@ -34,6 +34,7 @@ import org.geysermc.connector.network.translators.world.block.BlockTranslator; import org.geysermc.connector.utils.MathUtils; public class ChunkCache { + private static final int MINIMUM_WORLD_HEIGHT = 0; private final boolean cache; @@ -86,6 +87,11 @@ public class ChunkCache { return; } + if (y < MINIMUM_WORLD_HEIGHT || (y >> 4) > column.getChunks().length - 1) { + // Y likely goes above or below the height limit of this world + return; + } + Chunk chunk = column.getChunks()[y >> 4]; if (chunk != null) { chunk.set(x & 0xF, y & 0xF, z & 0xF, block); @@ -102,6 +108,11 @@ public class ChunkCache { return BlockTranslator.JAVA_AIR_ID; } + if (y < MINIMUM_WORLD_HEIGHT || (y >> 4) > column.getChunks().length - 1) { + // Y likely goes above or below the height limit of this world + return BlockTranslator.JAVA_AIR_ID; + } + Chunk chunk = column.getChunks()[y >> 4]; if (chunk != null) { return chunk.get(x & 0xF, y & 0xF, z & 0xF); From 4c6c397f37e0bba80a56f3e4389c1eb22ce2d1c3 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 23 Mar 2021 01:46:33 +0100 Subject: [PATCH 158/161] Fixed merge conflicts --- .../src/main/java/org/geysermc/connector/GeyserConnector.java | 2 +- .../network/translators/java/JavaJoinGameTranslator.java | 2 +- .../network/translators/java/JavaPluginMessageTranslator.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 3a40474f1..5e279c121 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -195,7 +195,7 @@ public class GeyserConnector { defaultAuthType = AuthType.getByName(config.getRemote().getAuthType()); - if (authType == AuthType.FLOODGATE) { + if (defaultAuthType == AuthType.FLOODGATE) { try { Key key = new AesKeyProducer().produceFrom(config.getFloodgateKeyPath()); cipher = new AesCipher(new Base64Topping()); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java index 17b5087ec..3e37f9eae 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaJoinGameTranslator.java @@ -97,7 +97,7 @@ public class JavaJoinGameTranslator extends PacketTranslator { @Override public void translate(ServerPluginMessagePacket packet, GeyserSession session) { - // The only plugin messages to listen for are Floodgate plugin messages - if (session.getConnector().getAuthType() != AuthType.FLOODGATE) { + // The only plugin messages it has to listen for are Floodgate plugin messages + if (session.getConnector().getDefaultAuthType() != AuthType.FLOODGATE) { return; } From 644ece124fbc505b8c9ee957b959108bc85765ba Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 23 Mar 2021 01:49:08 +0100 Subject: [PATCH 159/161] Bumped Geyser version to 1.3.0-SNAPSHOT --- bootstrap/bungeecord/pom.xml | 4 ++-- bootstrap/pom.xml | 2 +- bootstrap/spigot/pom.xml | 4 ++-- bootstrap/sponge/pom.xml | 4 ++-- bootstrap/standalone/pom.xml | 4 ++-- bootstrap/velocity/pom.xml | 4 ++-- common/pom.xml | 2 +- connector/pom.xml | 4 ++-- pom.xml | 2 +- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/bootstrap/bungeecord/pom.xml b/bootstrap/bungeecord/pom.xml index 10855aed4..5bbff0647 100644 --- a/bootstrap/bungeecord/pom.xml +++ b/bootstrap/bungeecord/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-bungeecord @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 4b61a65c3..d58cb2ca4 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-parent pom diff --git a/bootstrap/spigot/pom.xml b/bootstrap/spigot/pom.xml index d1cba8aea..dc9a95d5f 100644 --- a/bootstrap/spigot/pom.xml +++ b/bootstrap/spigot/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-spigot @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/bootstrap/sponge/pom.xml b/bootstrap/sponge/pom.xml index 97c4ac8a4..adeaa91de 100644 --- a/bootstrap/sponge/pom.xml +++ b/bootstrap/sponge/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-sponge @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/bootstrap/standalone/pom.xml b/bootstrap/standalone/pom.xml index 831239f66..6610af3b5 100644 --- a/bootstrap/standalone/pom.xml +++ b/bootstrap/standalone/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-standalone @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/bootstrap/velocity/pom.xml b/bootstrap/velocity/pom.xml index 58eee1f77..5dadc83c4 100644 --- a/bootstrap/velocity/pom.xml +++ b/bootstrap/velocity/pom.xml @@ -6,7 +6,7 @@ org.geysermc bootstrap-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT bootstrap-velocity @@ -14,7 +14,7 @@ org.geysermc connector - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/common/pom.xml b/common/pom.xml index 0f4d5421b..be90f0146 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT common diff --git a/connector/pom.xml b/connector/pom.xml index 4ae7e1383..4e244dd00 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -6,7 +6,7 @@ org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT connector @@ -20,7 +20,7 @@ org.geysermc common - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT compile diff --git a/pom.xml b/pom.xml index 011b320f4..959b7f953 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.geysermc geyser-parent - 1.2.0-SNAPSHOT + 1.3.0-SNAPSHOT pom Geyser Allows for players from Minecraft Bedrock Edition to join Minecraft Java Edition servers. From 2caf811750c3b195de05ff8ca42434be6b7c74c6 Mon Sep 17 00:00:00 2001 From: Tim203 Date: Tue, 23 Mar 2021 02:02:20 +0100 Subject: [PATCH 160/161] Deploy Floodgate 2.0 builds --- Jenkinsfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 09e88e86e..d76a6f737 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,7 +22,10 @@ pipeline { stage ('Deploy') { when { - branch "master" + anyOf { + branch "master" + branch "floodgate-2.0" + } } steps { From 677a8d68f659247fa73a9b7b44d3dfe006e79d5a Mon Sep 17 00:00:00 2001 From: Tim203 Date: Wed, 24 Mar 2021 15:50:13 +0100 Subject: [PATCH 161/161] Add an additional key searching fallback for Floodgate 2.0 --- .../org/geysermc/connector/FloodgateKeyLoader.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java index 9b9ee5681..7f5bca8c2 100644 --- a/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java +++ b/connector/src/main/java/org/geysermc/connector/FloodgateKeyLoader.java @@ -33,9 +33,18 @@ import java.nio.file.Path; public class FloodgateKeyLoader { public static Path getKeyPath(GeyserJacksonConfiguration config, Object floodgate, Path floodgateDataFolder, Path geyserDataFolder, GeyserLogger logger) { + if (!config.getRemote().getAuthType().equals("floodgate")) { + return geyserDataFolder.resolve(config.getFloodgateKeyFile()); + } + Path floodgateKey = geyserDataFolder.resolve(config.getFloodgateKeyFile()); - if (!Files.exists(floodgateKey) && config.getRemote().getAuthType().equals("floodgate")) { + if (config.getFloodgateKeyFile().equals("public-key.pem")) { + logger.info("Floodgate 2.0 doesn't use a public/private key system anymore. We'll search for key.pem instead"); + floodgateKey = geyserDataFolder.resolve("key.pem"); + } + + if (!Files.exists(floodgateKey)) { if (floodgate != null) { Path autoKey = floodgateDataFolder.resolve("key.pem"); if (Files.exists(autoKey)) {