From 3b274ef9d15d925cb8f2511e06ceb670b89a6484 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+DoctorMacc@users.noreply.github.com> Date: Mon, 14 Sep 2020 20:52:16 -0400 Subject: [PATCH] Pick block improvements (#1265) * Pick block improvements - Creative block picking is now implemented. If the survival-styled block picking fails, then the item is created, following Java-style mechanics. - Entity 'picking' is also implemented. The item is crafted using the same mechanics, and the same rules apply as normal block-picking (except it only works in creative mode, following Java. * Switch some logic around --- .../connector/inventory/PlayerInventory.java | 4 + ...drockBlockPickRequestPacketTranslator.java | 99 --------------- .../BedrockBlockPickRequestTranslator.java | 52 ++++++++ .../BedrockEntityPickRequestTranslator.java | 115 ++++++++++++++++++ .../connector/utils/InventoryUtils.java | 96 ++++++++++++++- 5 files changed, 266 insertions(+), 100 deletions(-) delete mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java create mode 100644 connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java 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 432ca827..225335a9 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java @@ -31,6 +31,10 @@ import lombok.Setter; public class PlayerInventory extends Inventory { + /** + * Stores the held item slot, starting at index 0. + * Add 36 in order to get the network item slot. + */ @Getter @Setter private int heldItemSlot; diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.java deleted file mode 100644 index 39cfceb1..00000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestPacketTranslator.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.bedrock; - -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientMoveItemToHotbarPacket; -import com.nukkitx.math.vector.Vector3i; -import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket; -import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; -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.item.ItemEntry; -import org.geysermc.connector.network.translators.item.ItemRegistry; -import org.geysermc.connector.network.translators.world.block.BlockTranslator; - -@Translator(packet = BlockPickRequestPacket.class) -public class BedrockBlockPickRequestPacketTranslator extends PacketTranslator { - - @Override - public void translate(BlockPickRequestPacket packet, GeyserSession session) { - Vector3i vector = packet.getBlockPosition(); - int blockToPick = session.getConnector().getWorldManager().getBlockAt(session, vector.getX(), vector.getY(), vector.getZ()); - - // Block is air - chunk caching is probably off - if (blockToPick == 0) { - return; - } - - // Get the inventory to choose a slot to pick - Inventory inventory = session.getInventoryCache().getOpenInventory(); - if (inventory == null) { - inventory = session.getInventory(); - } - - String targetIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockToPick).split("\\[")[0]; - - // Check hotbar for item - for (int i = 36; i < 45; i++) { - if (inventory.getItem(i) == null) { - continue; - } - ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); - // If this isn't the item we're looking for - if (!item.getJavaIdentifier().equals(targetIdentifier)) { - continue; - } - - PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket(); - hotbarPacket.setContainerId(0); - // Java inventory slot to hotbar slot ID - hotbarPacket.setSelectedHotbarSlot(i - 36); - hotbarPacket.setSelectHotbarSlot(true); - session.sendUpstreamPacket(hotbarPacket); - session.getInventory().setHeldItemSlot(i - 36); - // Don't check inventory if item was in hotbar - return; - } - - // Check inventory for item - for (int i = 9; i < 36; i++) { - if (inventory.getItem(i) == null) { - continue; - } - ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); - // If this isn't the item we're looking for - if (!item.getJavaIdentifier().equals(targetIdentifier)) { - continue; - } - - ClientMoveItemToHotbarPacket packetToSend = new ClientMoveItemToHotbarPacket(i); // https://wiki.vg/Protocol#Pick_Item - session.sendDownstreamPacket(packetToSend); - return; - } - } -} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.java new file mode 100644 index 00000000..023a83af --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockPickRequestTranslator.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.bedrock; + +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.packet.BlockPickRequestPacket; +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.world.block.BlockTranslator; +import org.geysermc.connector.utils.InventoryUtils; + +@Translator(packet = BlockPickRequestPacket.class) +public class BedrockBlockPickRequestTranslator extends PacketTranslator { + + @Override + public void translate(BlockPickRequestPacket packet, GeyserSession session) { + Vector3i vector = packet.getBlockPosition(); + int blockToPick = session.getConnector().getWorldManager().getBlockAt(session, vector.getX(), vector.getY(), vector.getZ()); + + // Block is air - chunk caching is probably off + if (blockToPick == 0) { + return; + } + + String targetIdentifier = BlockTranslator.getJavaIdBlockMap().inverse().get(blockToPick).split("\\[")[0]; + InventoryUtils.findOrCreatePickedBlock(session, targetIdentifier); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java new file mode 100644 index 00000000..4aa044ee --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockEntityPickRequestTranslator.java @@ -0,0 +1,115 @@ +/* + * 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.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.nukkitx.protocol.bedrock.data.entity.EntityData; +import com.nukkitx.protocol.bedrock.packet.EntityPickRequestPacket; +import org.geysermc.connector.entity.Entity; +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.utils.InventoryUtils; + +/** + * Called when the Bedrock user uses the pick block button on an entity + */ +@Translator(packet = EntityPickRequestPacket.class) +public class BedrockEntityPickRequestTranslator extends PacketTranslator { + + @Override + public void translate(EntityPickRequestPacket packet, GeyserSession session) { + if (session.getGameMode() != GameMode.CREATIVE) return; // Apparently Java behavior + Entity entity = session.getEntityCache().getEntityByGeyserId(packet.getRuntimeEntityId()); + if (entity == null) return; + + // Get the corresponding item + String itemName; + switch (entity.getEntityType()) { + case BOAT: + // Include type of boat in the name + int variant = entity.getMetadata().getInt(EntityData.VARIANT); + String typeOfBoat; + switch (variant) { + case 1: + typeOfBoat = "spruce"; + break; + case 2: + typeOfBoat = "birch"; + break; + case 3: + typeOfBoat = "jungle"; + break; + case 4: + typeOfBoat = "acacia"; + break; + case 5: + typeOfBoat = "dark_oak"; + break; + default: + typeOfBoat = "oak"; + break; + } + itemName = typeOfBoat + "_boat"; + break; + case LEASH_KNOT: + itemName = "lead"; + break; + case MINECART_CHEST: + case MINECART_COMMAND_BLOCK: + case MINECART_FURNACE: + case MINECART_HOPPER: + case MINECART_TNT: + // Move MINECART to the end of the name + itemName = entity.getEntityType().toString().toLowerCase().replace("minecart_", "") + "_minecart"; + break; + case MINECART_SPAWNER: + // Turns into a normal minecart + itemName = "minecart"; + break; + case ARMOR_STAND: + case END_CRYSTAL: + case ITEM_FRAME: + case MINECART: + case PAINTING: + // No spawn egg, just an item + itemName = entity.getEntityType().toString().toLowerCase(); + break; + default: + itemName = entity.getEntityType().toString().toLowerCase() + "_spawn_egg"; + break; + } + + String fullItemName = "minecraft:" + itemName; + ItemEntry entry = ItemRegistry.getItemEntry(fullItemName); + // Verify it is, indeed, an item + if (entry == null) return; + + InventoryUtils.findOrCreatePickedBlock(session, fullItemName); + } +} 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 cb51e2f3..b3cc14b9 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -26,6 +26,10 @@ package org.geysermc.connector.utils; 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.player.ClientPlayerChangeHeldItemPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientMoveItemToHotbarPacket; import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.nbt.NbtMap; import com.nukkitx.nbt.NbtMapBuilder; @@ -33,12 +37,14 @@ import com.nukkitx.nbt.NbtType; import com.nukkitx.protocol.bedrock.data.inventory.ContainerId; import com.nukkitx.protocol.bedrock.data.inventory.ItemData; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; -import org.geysermc.connector.common.ChatColor; +import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; import org.geysermc.connector.GeyserConnector; +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.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; @@ -151,4 +157,92 @@ public class InventoryUtils { root.put("display", display.build()); return ItemData.of(ItemRegistry.ITEM_ENTRIES.get(ItemRegistry.BARRIER_INDEX).getBedrockId(), (short) 0, 1, root.build()); } + + /** + * Attempt to find the specified item name in the session's inventory. + * If it is found and in the hotbar, set the user's held item to that slot. + * If it is found in another part of the inventory, move it. + * If it is not found and the user is in creative mode, create the item, + * overriding the current item slot if no other hotbar slots are empty, or otherwise selecting the empty slot. + * + * This attempts to mimic Java Edition behavior as best as it can. + * @param session the Bedrock client's session + * @param itemName the Java identifier of the item to search/select + */ + 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(); + } + + // Check hotbar for item + for (int i = 36; i < 45; i++) { + if (inventory.getItem(i) == null) { + continue; + } + ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); + // If this isn't the item we're looking for + if (!item.getJavaIdentifier().equals(itemName)) { + continue; + } + + setHotbarItem(session, i); + // Don't check inventory if item was in hotbar + return; + } + + // Check inventory for item + for (int i = 9; i < 36; i++) { + if (inventory.getItem(i) == null) { + continue; + } + ItemEntry item = ItemRegistry.getItem(inventory.getItem(i)); + // If this isn't the item we're looking for + if (!item.getJavaIdentifier().equals(itemName)) { + continue; + } + + ClientMoveItemToHotbarPacket packetToSend = new ClientMoveItemToHotbarPacket(i); // https://wiki.vg/Protocol#Pick_Item + session.sendDownstreamPacket(packetToSend); + return; + } + + // 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 + for (int i = 36; i < 45; i++) { + if (inventory.getItem(i) == null) { + slot = i; + break; + } + } + } + + ClientCreativeInventoryActionPacket actionPacket = new ClientCreativeInventoryActionPacket(slot, + new ItemStack(ItemRegistry.getItemEntry(itemName).getJavaId())); + if ((slot - 36) != session.getInventory().getHeldItemSlot()) { + setHotbarItem(session, slot); + } + session.sendDownstreamPacket(actionPacket); + } + } + + /** + * Changes the held item slot to the specified slot + * @param session GeyserSession + * @param slot inventory slot to be selected + */ + private static void setHotbarItem(GeyserSession session, int slot) { + PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket(); + hotbarPacket.setContainerId(0); + // Java inventory slot to hotbar slot ID + hotbarPacket.setSelectedHotbarSlot(slot - 36); + hotbarPacket.setSelectHotbarSlot(true); + session.sendUpstreamPacket(hotbarPacket); + ClientPlayerChangeHeldItemPacket heldItemPacket = new ClientPlayerChangeHeldItemPacket(slot); + session.sendDownstreamPacket(heldItemPacket); + session.getInventory().setHeldItemSlot(slot - 36); + } }