diff --git a/README.md b/README.md index 056a83ca..f11b9bfb 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,14 @@ Take a look [here](https://github.com/GeyserMC/Geyser/wiki#Setup) for how to set - Donate: https://patreon.com/GeyserMC ## What's Left to be Added/Fixed -- Inventories ([`inventory`](https://github.com/GeyserMC/Geyser/tree/inventory)) -- Crafting ([`inventory`](https://github.com/GeyserMC/Geyser/tree/inventory)) -- Creative Mode ([`inventory`](https://github.com/GeyserMC/Geyser/tree/inventory)) +- The Following Inventories + - [ ] Enchantment Table + - [ ] Beacon + - [ ] Cartography Table + - [ ] Stonecutter + - [ ] Villager Trading - Sounds - Block Particles -- Block Entities ([`inventory`](https://github.com/GeyserMC/Geyser/tree/inventory)) - Some Entity Flags ## Compiling diff --git a/connector/pom.xml b/connector/pom.xml index 5830ce26..9e223d4e 100644 --- a/connector/pom.xml +++ b/connector/pom.xml @@ -66,10 +66,34 @@ 8.3.1 compile + + com.nukkitx.fastutil + fastutil-int-double-maps + 8.3.1 + compile + + + com.nukkitx.fastutil + fastutil-int-boolean-maps + 8.3.1 + compile + + + com.nukkitx.fastutil + fastutil-object-int-maps + 8.3.1 + compile + + + com.nukkitx.fastutil + fastutil-object-byte-maps + 8.3.1 + compile + com.github.steveice10 opennbt - 1.3-SNAPSHOT + 1.4-SNAPSHOT compile diff --git a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java index 278e4859..2679857c 100644 --- a/connector/src/main/java/org/geysermc/connector/GeyserConnector.java +++ b/connector/src/main/java/org/geysermc/connector/GeyserConnector.java @@ -32,6 +32,7 @@ import com.nukkitx.protocol.bedrock.v390.Bedrock_v390; import lombok.Getter; import org.geysermc.common.AuthType; +import org.geysermc.common.IGeyserConfiguration; import org.geysermc.common.PlatformType; import org.geysermc.common.bootstrap.IGeyserBootstrap; import org.geysermc.common.logger.IGeyserLogger; @@ -43,7 +44,6 @@ import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.thread.PingPassthroughThread; import org.geysermc.connector.utils.Toolbox; -import org.geysermc.common.IGeyserConfiguration; import java.net.InetSocketAddress; import java.text.DecimalFormat; diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index fb48871b..d0ac2aeb 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -44,6 +44,7 @@ import org.geysermc.connector.entity.type.EntityType; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.scoreboard.Team; import org.geysermc.connector.utils.MessageUtils; +import org.geysermc.connector.network.session.cache.EntityEffectCache; import org.geysermc.connector.utils.SkinUtils; import java.util.UUID; @@ -55,6 +56,7 @@ public class PlayerEntity extends LivingEntity { private String username; private long lastSkinUpdate = -1; private boolean playerList = true; + private final EntityEffectCache effectCache; public PlayerEntity(GameProfile gameProfile, long entityId, long geyserId, Vector3f position, Vector3f motion, Vector3f rotation) { super(entityId, geyserId, EntityType.PLAYER, position, motion, rotation); @@ -62,6 +64,7 @@ public class PlayerEntity extends LivingEntity { profile = gameProfile; uuid = gameProfile.getId(); username = gameProfile.getName(); + effectCache = new EntityEffectCache(); if (geyserId == 1) valid = true; } diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java index f205c060..634f0674 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/AgeableEntity.java @@ -26,7 +26,6 @@ package org.geysermc.connector.entity.living; import com.github.steveice10.mc.protocol.data.game.entity.metadata.EntityMetadata; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.MetadataType; import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.data.EntityData; import com.nukkitx.protocol.bedrock.data.EntityFlag; diff --git a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java index 95d91ab8..b6a50520 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/living/animal/horse/HorseEntity.java @@ -45,4 +45,4 @@ public class HorseEntity extends AbstractHorseEntity { super.updateBedrockMetadata(entityMetadata, session); } -} +} \ No newline at end of file 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 d969f970..539fe1e2 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/Inventory.java @@ -27,9 +27,12 @@ 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.Setter; +import java.util.concurrent.atomic.AtomicInteger; + public class Inventory { @Getter @@ -43,16 +46,26 @@ public class Inventory { protected WindowType windowType; @Getter - protected int size; + protected final int size; @Getter @Setter protected String title; - @Getter @Setter protected ItemStack[] items; + @Getter + @Setter + protected Vector3i holderPosition = Vector3i.ZERO; + + @Getter + @Setter + protected long holderId = -1; + + @Getter + protected AtomicInteger transactionId = new AtomicInteger(1); + public Inventory(int id, WindowType windowType, int size) { this("Inventory", id, windowType, size); } @@ -62,11 +75,16 @@ public class Inventory { this.id = id; this.windowType = windowType; this.size = size; - this.items = new ItemStack[size]; } public ItemStack getItem(int slot) { return items[slot]; } + + public void setItem(int slot, ItemStack item) { + if (item != null && (item.getId() == 0 || item.getAmount() < 1)) + item = null; + items[slot] = item; + } } 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 a1db2fcd..432ca827 100644 --- a/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java +++ b/connector/src/main/java/org/geysermc/connector/inventory/PlayerInventory.java @@ -35,13 +35,21 @@ public class PlayerInventory extends Inventory { @Setter private int heldItemSlot; - public PlayerInventory() { - super(0, null, 45); + @Getter + private ItemStack cursor; + 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() { - return items[heldItemSlot]; + return items[36 + heldItemSlot]; } } 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 83a94529..12ae39bf 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 @@ -44,7 +44,13 @@ import com.nukkitx.math.vector.Vector3f; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.nbt.tag.CompoundTag; import com.nukkitx.protocol.bedrock.BedrockServerSession; +import com.nukkitx.protocol.bedrock.data.ContainerId; import com.nukkitx.protocol.bedrock.data.GamePublishSetting; +import com.nukkitx.protocol.bedrock.packet.AvailableEntityIdentifiersPacket; +import com.nukkitx.protocol.bedrock.packet.BiomeDefinitionListPacket; +import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; +import com.nukkitx.protocol.bedrock.packet.StartGamePacket; +import com.nukkitx.protocol.bedrock.packet.TextPacket; import com.nukkitx.protocol.bedrock.data.GameRuleData; import com.nukkitx.protocol.bedrock.data.PlayerPermission; import com.nukkitx.protocol.bedrock.packet.*; @@ -125,6 +131,9 @@ public class GeyserSession implements CommandSender { private boolean manyDimPackets = false; private ServerRespawnPacket lastDimPacket = null; + @Setter + private int craftSlot = 0; + public GeyserSession(GeyserConnector connector, BedrockServerSession bedrockServerSession) { this.connector = connector; this.upstream = new UpstreamSession(bedrockServerSession); @@ -160,6 +169,11 @@ public class GeyserSession implements CommandSender { entityPacket.setTag(CompoundTag.EMPTY); upstream.sendPacket(entityPacket); + InventoryContentPacket creativePacket = new InventoryContentPacket(); + creativePacket.setContainerId(ContainerId.CREATIVE); + creativePacket.setContents(Toolbox.CREATIVE_ITEMS); + upstream.sendPacket(creativePacket); + PlayStatusPacket playStatusPacket = new PlayStatusPacket(); playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN); upstream.sendPacket(playStatusPacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityEffectCache.java b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityEffectCache.java new file mode 100644 index 00000000..a16ef690 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/session/cache/EntityEffectCache.java @@ -0,0 +1,54 @@ +/* + * 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 com.github.steveice10.mc.protocol.data.game.entity.Effect; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import lombok.Getter; + +public class EntityEffectCache { + + @Getter + private final Object2IntMap entityEffects = new Object2IntOpenHashMap<>(); + + public void addEffect(Effect effect, int effectAmplifier) { + if (effect != null) { + entityEffects.putIfAbsent(effect, effectAmplifier + 1); + } + } + + public void removeEffect(Effect effect) { + if (entityEffects.containsKey(effect)) { + int effectLevel = entityEffects.getInt(effect); + entityEffects.remove(effect, effectLevel); + } + } + + public int getEffectLevel(Effect effect) { + return entityEffects.getOrDefault(effect, 0); + } +} 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 index 7a505878..8734c710 100644 --- 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 @@ -25,15 +25,12 @@ package org.geysermc.connector.network.session.cache; -import com.github.steveice10.packetlib.packet.Packet; import lombok.Getter; import lombok.Setter; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; public class InventoryCache { @@ -47,9 +44,6 @@ public class InventoryCache { @Getter private Map inventories = new HashMap(); - @Getter - private Map> cachedPackets = new HashMap>(); - public InventoryCache(GeyserSession session) { this.session = session; } @@ -65,10 +59,4 @@ public class InventoryCache { public void uncacheInventory(int id) { inventories.remove(id); } - - public void cachePacket(int id, Packet packet) { - List packets = cachedPackets.getOrDefault(id, new ArrayList()); - packets.add(packet); - cachedPackets.put(id, packets); - } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/Translators.java b/connector/src/main/java/org/geysermc/connector/network/translators/Translators.java index d4e5dae8..cffb85f1 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/Translators.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/Translators.java @@ -27,11 +27,17 @@ package org.geysermc.connector.network.translators; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import com.github.steveice10.mc.protocol.data.game.window.WindowType; +import com.nukkitx.protocol.bedrock.data.ContainerType; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.block.BlockTranslator; -import org.geysermc.connector.network.translators.inventory.GenericInventoryTranslator; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.network.translators.block.entity.*; +import org.geysermc.connector.network.translators.inventory.*; +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.ItemTranslator; import org.reflections.Reflections; @@ -50,7 +56,10 @@ public class Translators { private static ItemTranslator itemTranslator; @Getter - private static InventoryTranslator inventoryTranslator = new GenericInventoryTranslator(); + private static Map inventoryTranslators = new HashMap<>(); + + @Getter + private static Map blockEntityTranslators = new HashMap<>(); private static final CompoundTag EMPTY_TAG = CompoundTagBuilder.builder().buildRootTag(); public static final byte[] EMPTY_LEVEL_CHUNK_DATA; @@ -82,15 +91,15 @@ public class Translators { if (Packet.class.isAssignableFrom(packet)) { Class targetPacket = (Class) packet; PacketTranslator translator = (PacketTranslator) clazz.newInstance(); - + Registry.registerJava(targetPacket, translator); - + } else if (BedrockPacket.class.isAssignableFrom(packet)) { Class targetPacket = (Class) packet; PacketTranslator translator = (PacketTranslator) clazz.newInstance(); - + Registry.registerBedrock(targetPacket, translator); - + } else { GeyserConnector.getInstance().getLogger().error("Class " + clazz.getCanonicalName() + " is annotated as a translator but has an invalid target packet."); } @@ -102,15 +111,48 @@ public class Translators { itemTranslator = new ItemTranslator(); BlockTranslator.init(); + registerBlockEntityTranslators(); registerInventoryTranslators(); } + private static void registerBlockEntityTranslators() { + Reflections ref = new Reflections("org.geysermc.connector.network.translators.block.entity"); + + for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { + + GeyserConnector.getInstance().getLogger().debug("Found annotated block entity: " + clazz.getCanonicalName()); + + try { + blockEntityTranslators.put(clazz.getAnnotation(BlockEntity.class).name(), (BlockEntityTranslator) clazz.newInstance()); + } catch (InstantiationException | IllegalAccessException e) { + GeyserConnector.getInstance().getLogger().error("Could not instantiate annotated block entity " + clazz.getCanonicalName() + "."); + } + } + } + private static void registerInventoryTranslators() { - /*inventoryTranslators.put(WindowType.GENERIC_9X1, new GenericInventoryTranslator()); - inventoryTranslators.put(WindowType.GENERIC_9X2, new GenericInventoryTranslator()); - inventoryTranslators.put(WindowType.GENERIC_9X3, new GenericInventoryTranslator()); - inventoryTranslators.put(WindowType.GENERIC_9X4, new GenericInventoryTranslator()); - inventoryTranslators.put(WindowType.GENERIC_9X5, new GenericInventoryTranslator()); - inventoryTranslators.put(WindowType.GENERIC_9X6, new GenericInventoryTranslator());*/ + inventoryTranslators.put(null, new PlayerInventoryTranslator()); //player inventory + inventoryTranslators.put(WindowType.GENERIC_9X1, new SingleChestInventoryTranslator(9)); + inventoryTranslators.put(WindowType.GENERIC_9X2, new SingleChestInventoryTranslator(18)); + inventoryTranslators.put(WindowType.GENERIC_9X3, new SingleChestInventoryTranslator(27)); + inventoryTranslators.put(WindowType.GENERIC_9X4, new DoubleChestInventoryTranslator(36)); + inventoryTranslators.put(WindowType.GENERIC_9X5, new DoubleChestInventoryTranslator(45)); + inventoryTranslators.put(WindowType.GENERIC_9X6, new DoubleChestInventoryTranslator(54)); + inventoryTranslators.put(WindowType.BREWING_STAND, new BrewingInventoryTranslator()); + inventoryTranslators.put(WindowType.ANVIL, new AnvilInventoryTranslator()); + inventoryTranslators.put(WindowType.CRAFTING, new CraftingInventoryTranslator()); + inventoryTranslators.put(WindowType.GRINDSTONE, new GrindstoneInventoryTranslator()); + //inventoryTranslators.put(WindowType.ENCHANTMENT, new EnchantmentInventoryTranslator()); //TODO + + InventoryTranslator furnace = new FurnaceInventoryTranslator(); + inventoryTranslators.put(WindowType.FURNACE, furnace); + inventoryTranslators.put(WindowType.BLAST_FURNACE, furnace); + inventoryTranslators.put(WindowType.SMOKER, furnace); + + InventoryUpdater containerUpdater = new ContainerInventoryUpdater(); + inventoryTranslators.put(WindowType.GENERIC_3X3, new BlockInventoryTranslator(9, "minecraft:dispenser[facing=north,triggered=false]", ContainerType.DISPENSER, containerUpdater)); + inventoryTranslators.put(WindowType.HOPPER, new BlockInventoryTranslator(5, "minecraft:hopper[enabled=false,facing=down]", ContainerType.HOPPER, containerUpdater)); + inventoryTranslators.put(WindowType.SHULKER_BOX, new BlockInventoryTranslator(27, "minecraft:shulker_box[facing=north]", ContainerType.CONTAINER, containerUpdater)); + //inventoryTranslators.put(WindowType.BEACON, new BlockInventoryTranslator(1, "minecraft:beacon", ContainerType.BEACON)); //TODO } } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java new file mode 100644 index 00000000..dc076a91 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockBlockEntityDataTranslator.java @@ -0,0 +1,92 @@ +/* + * 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.metadata.Position; +import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientUpdateSignPacket; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +import java.util.HashMap; +import java.util.Map; + +@Translator(packet = BlockEntityDataPacket.class) +public class BedrockBlockEntityDataTranslator extends PacketTranslator { + + // In case two people are editing signs at the same time this array holds the temporary messages to be sent + // Position -> Message being held + protected static Map lastMessages = new HashMap<>(); + + @Override + public void translate(BlockEntityDataPacket packet, GeyserSession session) { + if (packet.getData() instanceof CompoundTag) { + CompoundTag tag = (CompoundTag) packet.getData(); + if (tag.getString("id").equals("Sign")) { + // This is the reason why this all works - Bedrock sends packets every time you update the sign, Java only wants the final packet + // But Bedrock sends one final packet when you're done editing the sign, which should be equal to the last message since there's no edits + // So if the latest update does not match the last cached update then it's still being edited + Position pos = new Position(tag.getInt("x"), tag.getInt("y"), tag.getInt("z")); + if (!tag.getString("Text").equals(lastMessages.get(pos))) { + lastMessages.put(pos, tag.getString("Text")); + return; + } + // Otherwise the two messages are identical and we can get to work deconstructing + StringBuilder newMessage = new StringBuilder(); + // While Bedrock's sign lines are one string, Java's is an array of each line + // (Initialized all with empty strings because it complains about null) + String[] lines = new String[] {"", "", "", ""}; + int iterator = 0; + // This converts the message into the array'd message Java wants + for (char character : tag.getString("Text").toCharArray()) { + // If we get a return in Bedrock, that signals to use the next line. + if (character == '\n') { + lines[iterator] = newMessage.toString(); + iterator++; + // Bedrock, for whatever reason, can hold a message out of bounds + // We don't care about that so we discard that + if (iterator > lines.length - 1) { + break; + } + newMessage = new StringBuilder(); + } else newMessage.append(character); + } + // Put the final line on since it isn't done in the for loop + if (iterator < lines.length) lines[iterator] = newMessage.toString(); + ClientUpdateSignPacket clientUpdateSignPacket = new ClientUpdateSignPacket(pos, lines); + session.getDownstream().getSession().send(clientUpdateSignPacket); + //TODO (potentially): originally I was going to update the sign blocks so Bedrock and Java users would match visually + // However Java can still store a lot per-line and visuals are still messed up so that doesn't work + + // We remove the sign position from map to indicate there is no work-in-progress sign + lastMessages.remove(pos); + } + } + + } +} 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 new file mode 100644 index 00000000..8d036bfa --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockContainerCloseTranslator.java @@ -0,0 +1,54 @@ +/* + * 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.ClientCloseWindowPacket; +import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket; +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.utils.InventoryUtils; + +@Translator(packet = ContainerClosePacket.class) +public class BedrockContainerCloseTranslator extends PacketTranslator { + + @Override + public void translate(ContainerClosePacket packet, GeyserSession session) { + byte windowId = packet.getWindowId(); + if (windowId == -1) { //player inventory or crafting table + Inventory openInventory = session.getInventoryCache().getOpenInventory(); + if (openInventory != null) { + windowId = (byte) openInventory.getId(); + } else { + windowId = 0; + } + } + ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(windowId); + session.getDownstream().getSession().send(closeWindowPacket); + InventoryUtils.closeInventory(session, windowId); + } +} 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 0f61925b..fd23cd3a 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 @@ -27,10 +27,14 @@ package org.geysermc.connector.network.translators.bedrock; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerPlaceBlockPacket; import org.geysermc.connector.entity.Entity; +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.Translators; +import org.geysermc.connector.utils.InventoryUtils; +import com.nukkitx.math.vector.Vector3f; 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; @@ -40,7 +44,6 @@ 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.ClientPlayerInteractEntityPacket; import com.github.steveice10.mc.protocol.packet.ingame.client.player.ClientPlayerUseItemPacket; -import com.nukkitx.math.vector.Vector3f; import com.nukkitx.protocol.bedrock.packet.InventoryTransactionPacket; @Translator(packet = InventoryTransactionPacket.class) @@ -49,6 +52,17 @@ public class BedrockInventoryTransactionTranslator extends PacketTranslator BANNER_COLORS = new Object2IntOpenHashMap<>(); + private static final Object2ByteMap BED_COLORS = new Object2ByteOpenHashMap<>(); + private static final Object2ByteMap SKULL_VARIANTS = new Object2ByteOpenHashMap<>(); + private static final Object2ByteMap SKULL_ROTATIONS = new Object2ByteOpenHashMap<>(); + + /** + * Determines if the block state contains Bedrock block information + * @param entry The String -> JsonNode map used in BlockTranslator + * @param javaBlockState the Java Block State of the block + */ + public static void storeBlockStateValues(Map.Entry entry, BlockState javaBlockState) { + JsonNode bannerColor = entry.getValue().get("banner_color"); + if (bannerColor != null) { + BlockStateValues.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"); + if (bedColor != null) { + BlockStateValues.BED_COLORS.put(javaBlockState, (byte) bedColor.intValue()); + return; + } + + JsonNode skullVariation = entry.getValue().get("variation"); + if(skullVariation != null) { + BlockStateValues.SKULL_VARIANTS.put(javaBlockState, (byte) skullVariation.intValue()); + } + + JsonNode skullRotation = entry.getValue().get("skull_rotation"); + if (skullRotation != null) { + BlockStateValues.SKULL_ROTATIONS.put(javaBlockState, (byte) skullRotation.intValue()); + } + } + + /** + * Banner colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives an integer color that Bedrock can use. + * @param state BlockState of the block + * @return banner color integer or -1 if no color + */ + public static int getBannerColor(BlockState state) { + if (BANNER_COLORS.containsKey(state)) { + return BANNER_COLORS.getInt(state); + } + return -1; + } + + /** + * Bed colors are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte color that Bedrock can use - Bedrock needs a byte in the final tag. + * @param state BlockState of the block + * @return bed color byte or -1 if no color + */ + public static byte getBedColor(BlockState state) { + if (BED_COLORS.containsKey(state)) { + return BED_COLORS.getByte(state); + } + return -1; + } + + /** + * Skull variations are part of the namespaced ID in Java Edition, but part of the block entity tag in Bedrock. + * This gives a byte variant ID that Bedrock can use. + * @param state BlockState of the block + * @return skull variant byte or -1 if no variant + */ + public static byte getSkullVariant(BlockState state) { + if (SKULL_VARIANTS.containsKey(state)) { + return SKULL_VARIANTS.getByte(state); + } + return -1; + } + + /** + * + * @param state BlockState of the block + * @return skull rotation value or -1 if no value + */ + public static byte getSkullRotation(BlockState state) { + if (SKULL_ROTATIONS.containsKey(state)) { + return SKULL_ROTATIONS.getByte(state); + } + return -1; + } + +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java index 58f6ff4e..5b5d5c9c 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/BlockTranslator.java @@ -32,6 +32,10 @@ import com.nukkitx.nbt.NbtUtils; import com.nukkitx.nbt.stream.NBTInputStream; import com.nukkitx.nbt.tag.CompoundTag; import com.nukkitx.nbt.tag.ListTag; +import it.unimi.dsi.fastutil.ints.Int2BooleanMap; +import it.unimi.dsi.fastutil.ints.Int2BooleanOpenHashMap; +import it.unimi.dsi.fastutil.ints.Int2DoubleMap; +import it.unimi.dsi.fastutil.ints.Int2DoubleOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -41,7 +45,9 @@ import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import org.geysermc.connector.GeyserConnector; +import org.geysermc.connector.network.translators.block.entity.BlockEntity; import org.geysermc.connector.utils.Toolbox; +import org.reflections.Reflections; import java.io.InputStream; import java.util.*; @@ -53,11 +59,22 @@ public class BlockTranslator { private static final Int2IntMap JAVA_TO_BEDROCK_BLOCK_MAP = new Int2IntOpenHashMap(); private static final Int2ObjectMap BEDROCK_TO_JAVA_BLOCK_MAP = new Int2ObjectOpenHashMap<>(); + private static final Map JAVA_ID_BLOCK_MAP = new HashMap<>(); private static final IntSet WATERLOGGED = new IntOpenHashSet(); // Bedrock carpet ID, used in LlamaEntity.java for decoration public static final int CARPET = 171; + private static final Map JAVA_ID_TO_BLOCK_ENTITY_MAP = new HashMap<>(); + + public static final Int2DoubleMap JAVA_RUNTIME_ID_TO_HARDNESS = new Int2DoubleOpenHashMap(); + public static final Int2BooleanMap JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND = new Int2BooleanOpenHashMap(); + public static final Int2ObjectMap JAVA_RUNTIME_ID_TO_TOOL_TYPE = new Int2ObjectOpenHashMap<>(); + + // For block breaking animation math + public static final IntSet JAVA_RUNTIME_WOOL_IDS = new IntOpenHashSet(); + public static final int JAVA_RUNTIME_COBWEB_ID; + private static final int BLOCK_STATE_VERSION = 17760256; static { @@ -90,16 +107,58 @@ public class BlockTranslator { addedStatesMap.defaultReturnValue(-1); List paletteList = new ArrayList<>(); + Reflections ref = new Reflections("org.geysermc.connector.network.translators.block.entity"); + ref.getTypesAnnotatedWith(BlockEntity.class); + int waterRuntimeId = -1; int javaRuntimeId = -1; int bedrockRuntimeId = 0; + int cobwebRuntimeId = -1; Iterator> blocksIterator = blocks.fields(); while (blocksIterator.hasNext()) { javaRuntimeId++; Map.Entry entry = blocksIterator.next(); String javaId = entry.getKey(); + BlockState javaBlockState = new BlockState(javaRuntimeId); CompoundTag blockTag = buildBedrockState(entry.getValue()); + // TODO fix this, (no block should have a null hardness) + JsonNode hardnessNode = entry.getValue().get("block_hardness"); + if (hardnessNode != null) { + JAVA_RUNTIME_ID_TO_HARDNESS.put(javaRuntimeId, hardnessNode.doubleValue()); + } + + JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.put(javaRuntimeId, entry.getValue().get("can_break_with_hand").booleanValue()); + + JsonNode toolTypeNode = entry.getValue().get("tool_type"); + if (toolTypeNode != null) { + JAVA_RUNTIME_ID_TO_TOOL_TYPE.put(javaRuntimeId, toolTypeNode.textValue()); + } + + if (javaId.contains("wool")) { + JAVA_RUNTIME_WOOL_IDS.add(javaRuntimeId); + } + + if (javaId.contains("cobweb")) { + cobwebRuntimeId = javaRuntimeId; + } + + JAVA_ID_BLOCK_MAP.put(javaId, javaBlockState); + + // Used for adding all "special" Java block states to block state map + String identifier; + String bedrock_identifer = entry.getValue().get("bedrock_identifier").asText(); + for (Class clazz : ref.getTypesAnnotatedWith(BlockEntity.class)) { + identifier = clazz.getAnnotation(BlockEntity.class).regex(); + // Endswith, or else the block bedrock gets picked up for bed + if (bedrock_identifer.endsWith(identifier) && !identifier.equals("")) { + JAVA_ID_TO_BLOCK_ENTITY_MAP.put(javaBlockState, clazz.getAnnotation(BlockEntity.class).name()); + break; + } + } + + BlockStateValues.storeBlockStateValues(entry, javaBlockState); + if ("minecraft:water[level=0]".equals(javaId)) { waterRuntimeId = bedrockRuntimeId; } @@ -107,10 +166,10 @@ public class BlockTranslator { || javaId.contains("minecraft:bubble_column") || javaId.contains("minecraft:kelp") || javaId.contains("seagrass"); if (waterlogged) { - BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId | 1 << 31, new BlockState(javaRuntimeId)); + BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId | 1 << 31, javaBlockState); WATERLOGGED.add(javaRuntimeId); } else { - BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId, new BlockState(javaRuntimeId)); + BEDROCK_TO_JAVA_BLOCK_MAP.putIfAbsent(bedrockRuntimeId, javaBlockState); } CompoundTag runtimeTag = blockStateMap.remove(blockTag); @@ -118,7 +177,7 @@ public class BlockTranslator { addedStatesMap.put(blockTag, bedrockRuntimeId); paletteList.add(runtimeTag); } else { - int duplicateRuntimeId = addedStatesMap.get(blockTag); + int duplicateRuntimeId = addedStatesMap.getOrDefault(blockTag, -1); if (duplicateRuntimeId == -1) { GeyserConnector.getInstance().getLogger().debug("Mapping " + javaId + " was not found for bedrock edition!"); } else { @@ -131,6 +190,11 @@ public class BlockTranslator { bedrockRuntimeId++; } + if (cobwebRuntimeId == -1) { + throw new AssertionError("Unable to find cobwebs in palette"); + } + JAVA_RUNTIME_COBWEB_ID = cobwebRuntimeId; + if (waterRuntimeId == -1) { throw new AssertionError("Unable to find water in palette"); } @@ -189,6 +253,14 @@ public class BlockTranslator { return BEDROCK_TO_JAVA_BLOCK_MAP.get(bedrockId); } + public static BlockState getJavaBlockState(String javaId) { + return JAVA_ID_BLOCK_MAP.get(javaId); + } + + public static String getBlockEntityString(BlockState javaId) { + return JAVA_ID_TO_BLOCK_ENTITY_MAP.get(javaId); + } + public static boolean isWaterlogged(BlockState state) { return WATERLOGGED.contains(state.getId()); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BannerBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BannerBlockEntityTranslator.java new file mode 100644 index 00000000..22d26cb4 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BannerBlockEntityTranslator.java @@ -0,0 +1,91 @@ +/* + * 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.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.IntTag; +import com.nukkitx.nbt.tag.StringTag; +import com.nukkitx.nbt.tag.Tag; +import org.geysermc.connector.network.translators.block.BlockStateValues; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "Banner", delay = false, regex = "banner") +public class BannerBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { + + @Override + public boolean isBlock(BlockState blockState) { + return BlockStateValues.getBannerColor(blockState) != -1; + } + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + int bannerColor = BlockStateValues.getBannerColor(blockState); + if (bannerColor != -1) { + tags.add(new IntTag("Base", 15 - bannerColor)); + } + ListTag patterns = tag.get("Patterns"); + List tagsList = new ArrayList<>(); + if (tag.contains("Patterns")) { + for (com.github.steveice10.opennbt.tag.builtin.Tag patternTag : patterns.getValue()) { + tagsList.add(getPattern((CompoundTag) patternTag)); + } + com.nukkitx.nbt.tag.ListTag bedrockPatterns = + new com.nukkitx.nbt.tag.ListTag<>("Patterns", com.nukkitx.nbt.tag.CompoundTag.class, tagsList); + tags.add(bedrockPatterns); + } + if (tag.contains("CustomName")) { + tags.add(new StringTag("CustomName", (String) tag.get("CustomName").getValue())); + } + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + CompoundTag tag = getConstantJavaTag(javaId, x, y, z); + tag.put(new ListTag("Patterns")); + return tag; + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + tagBuilder.listTag("Patterns", com.nukkitx.nbt.tag.CompoundTag.class, new ArrayList<>()); + return tagBuilder.buildRootTag(); + } + + protected com.nukkitx.nbt.tag.CompoundTag getPattern(CompoundTag pattern) { + return CompoundTagBuilder.builder() + .intTag("Color", 15 - (int) pattern.get("Color").getValue()) + .stringTag("Pattern", (String) pattern.get("Pattern").getValue()) + .buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BedBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BedBlockEntityTranslator.java new file mode 100644 index 00000000..a8dae253 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BedBlockEntityTranslator.java @@ -0,0 +1,67 @@ +/* + * 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.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.ByteTag; +import com.nukkitx.nbt.tag.Tag; +import org.geysermc.connector.network.translators.block.BlockStateValues; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "Bed", delay = false, regex = "bed") +public class BedBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { + + @Override + public boolean isBlock(BlockState blockState) { + return BlockStateValues.getBedColor(blockState) != -1; + } + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + byte bedcolor = BlockStateValues.getBedColor(blockState); + // Just in case... + if (bedcolor == -1) bedcolor = 0; + tags.add(new ByteTag("color", bedcolor)); + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + return null; + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + tagBuilder.byteTag("color", (byte) 0); + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntity.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntity.java new file mode 100644 index 00000000..0321f2c1 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntity.java @@ -0,0 +1,45 @@ +/* + * 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.block.entity; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(value = RetentionPolicy.RUNTIME) +public @interface BlockEntity { + /** + * Whether to delay the sending of the block entity + */ + boolean delay(); + /** + * The block entity name + */ + String name(); + /** + * The search term used in BlockTranslator + */ + String regex(); +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntityTranslator.java new file mode 100644 index 00000000..f2825789 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/BlockEntityTranslator.java @@ -0,0 +1,79 @@ +/* + * 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.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.IntTag; +import com.github.steveice10.opennbt.tag.builtin.StringTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.Tag; + +import org.geysermc.connector.utils.BlockEntityUtils; + +import java.util.List; + +public abstract class BlockEntityTranslator { + + public abstract List> translateTag(CompoundTag tag, BlockState blockState); + + public abstract CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z); + + public abstract com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z); + + public com.nukkitx.nbt.tag.CompoundTag getBlockEntityTag(String id, CompoundTag tag, BlockState blockState) { + int x = Integer.parseInt(String.valueOf(tag.getValue().get("x").getValue())); + int y = Integer.parseInt(String.valueOf(tag.getValue().get("y").getValue())); + int z = Integer.parseInt(String.valueOf(tag.getValue().get("z").getValue())); + + CompoundTagBuilder tagBuilder = getConstantBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(id), x, y, z).toBuilder(); + translateTag(tag, blockState).forEach(tagBuilder::tag); + return tagBuilder.buildRootTag(); + } + + protected CompoundTag getConstantJavaTag(String javaId, int x, int y, int z) { + CompoundTag tag = new CompoundTag(""); + tag.put(new IntTag("x", x)); + tag.put(new IntTag("y", y)); + tag.put(new IntTag("z", z)); + tag.put(new StringTag("id", javaId)); + return tag; + } + + protected com.nukkitx.nbt.tag.CompoundTag getConstantBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() + .intTag("x", x) + .intTag("y", y) + .intTag("z", z) + .stringTag("id", bedrockId); + return tagBuilder.buildRootTag(); + } + + @SuppressWarnings("unchecked") + protected T getOrDefault(com.github.steveice10.opennbt.tag.builtin.Tag tag, T defaultValue) { + return (tag != null && tag.getValue() != null) ? (T) tag.getValue() : defaultValue; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/CampfireBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/CampfireBlockEntityTranslator.java new file mode 100644 index 00000000..11b7e864 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/CampfireBlockEntityTranslator.java @@ -0,0 +1,82 @@ +/* + * 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.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.ListTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.Tag; + +import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.item.ItemEntry; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +@BlockEntity(name = "Campfire", delay = false, regex = "campfire") +public class CampfireBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + ListTag items = tag.get("Items"); + int i = 1; + for (com.github.steveice10.opennbt.tag.builtin.Tag itemTag : items.getValue()) { + tags.add(getItem((CompoundTag) itemTag).toBuilder().build("Item" + i)); + i++; + } + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + CompoundTag tag = getConstantJavaTag(javaId, x, y, z); + tag.put(new ListTag("Items")); + return tag; + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + tagBuilder.tag(new com.nukkitx.nbt.tag.CompoundTag("Item1", new HashMap<>())); + tagBuilder.tag(new com.nukkitx.nbt.tag.CompoundTag("Item2", new HashMap<>())); + tagBuilder.tag(new com.nukkitx.nbt.tag.CompoundTag("Item3", new HashMap<>())); + tagBuilder.tag(new com.nukkitx.nbt.tag.CompoundTag("Item4", new HashMap<>())); + return tagBuilder.buildRootTag(); + } + + protected com.nukkitx.nbt.tag.CompoundTag getItem(CompoundTag tag) { + ItemEntry entry = Translators.getItemTranslator().getItemEntry((String) tag.get("id").getValue()); + CompoundTagBuilder tagBuilder = CompoundTagBuilder.builder() + .shortTag("id", (short) entry.getBedrockId()) + .byteTag("Count", (byte) tag.get("Count").getValue()) + .shortTag("Damage", (short) entry.getBedrockData()) + .tag(CompoundTagBuilder.builder().build("tag")); + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EmptyBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EmptyBlockEntityTranslator.java new file mode 100644 index 00000000..e4601400 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EmptyBlockEntityTranslator.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.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.tag.Tag; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "Empty", delay = false, regex = "") +public class EmptyBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + return new ArrayList<>(); + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + return getConstantJavaTag(javaId, x, y, z); + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + return getConstantBedrockTag(bedrockId, x, y, z); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EndGatewayBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EndGatewayBlockEntityTranslator.java new file mode 100644 index 00000000..de5868a4 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/EndGatewayBlockEntityTranslator.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.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.github.steveice10.opennbt.tag.builtin.LongTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.IntTag; +import com.nukkitx.nbt.tag.Tag; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; + +@BlockEntity(name = "EndGateway", delay = true, regex = "end_gateway") +public class EndGatewayBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + tags.add(new IntTag("Age", (int) (long) tag.get("Age").getValue())); + // Java sometimes does not provide this tag, but Bedrock crashes if it doesn't exist + // Linked coordinates + List tagsList = new ArrayList<>(); + // Yes, the axis letters are capitalized + tagsList.add(new IntTag("", getExitPortalCoordinate(tag, "X"))); + tagsList.add(new IntTag("", getExitPortalCoordinate(tag, "Y"))); + tagsList.add(new IntTag("", getExitPortalCoordinate(tag, "Z"))); + com.nukkitx.nbt.tag.ListTag exitPortal = + new com.nukkitx.nbt.tag.ListTag<>("ExitPortal", IntTag.class, tagsList); + tags.add(exitPortal); + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + CompoundTag tag = getConstantJavaTag(javaId, x, y, z); + tag.put(new LongTag("Age")); + return tag; + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + List tagsList = new ArrayList<>(); + tagsList.add(new IntTag("", 0)); + tagsList.add(new IntTag("", 0)); + tagsList.add(new IntTag("", 0)); + tagBuilder.listTag("ExitPortal", IntTag.class, tagsList); + return tagBuilder.buildRootTag(); + } + + private int getExitPortalCoordinate(CompoundTag tag, String axis) { + // Return 0 if it doesn't exist, otherwise give proper value + if (tag.get("ExitPortal") != null) { + LinkedHashMap compoundTag = (LinkedHashMap) tag.get("ExitPortal").getValue(); + com.github.steveice10.opennbt.tag.builtin.IntTag intTag = (com.github.steveice10.opennbt.tag.builtin.IntTag) compoundTag.get(axis); + return intTag.getValue(); + } return 0; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/RequiresBlockState.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/RequiresBlockState.java new file mode 100644 index 00000000..ed8e6ede --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/RequiresBlockState.java @@ -0,0 +1,42 @@ +/* + * 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.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; + +/** + * Implemented in block entities if their Java block state is required for additional values in Bedrock + */ +public interface RequiresBlockState { + + /** + * Determines if block is part of class + * @param blockState BlockState to be compared + * @return true if part of the class + */ + boolean isBlock(BlockState blockState); + +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SignBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SignBlockEntityTranslator.java new file mode 100644 index 00000000..74dcdd13 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SignBlockEntityTranslator.java @@ -0,0 +1,76 @@ +/* + * 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.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.mc.protocol.data.message.Message; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.StringTag; +import com.nukkitx.nbt.tag.Tag; +import org.geysermc.connector.utils.MessageUtils; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "Sign", delay = true, regex = "sign") +public class SignBlockEntityTranslator extends BlockEntityTranslator { + + @Override + public List> translateTag(CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + + String line1 = getOrDefault(tag.getValue().get("Text1"), ""); + String line2 = getOrDefault(tag.getValue().get("Text2"), ""); + String line3 = getOrDefault(tag.getValue().get("Text3"), ""); + String line4 = getOrDefault(tag.getValue().get("Text4"), ""); + + tags.add(new StringTag("Text", MessageUtils.getBedrockMessage(Message.fromString(line1)) + + "\n" + MessageUtils.getBedrockMessage(Message.fromString(line2)) + + "\n" + MessageUtils.getBedrockMessage(Message.fromString(line3)) + + "\n" + MessageUtils.getBedrockMessage(Message.fromString(line4)) + )); + + return tags; + } + + @Override + public CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + CompoundTag tag = getConstantJavaTag(javaId, x, y, z); + tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text1", "{\"text\":\"\"}")); + tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text2", "{\"text\":\"\"}")); + tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text3", "{\"text\":\"\"}")); + tag.put(new com.github.steveice10.opennbt.tag.builtin.StringTag("Text4", "{\"text\":\"\"}")); + return tag; + } + + @Override + public com.nukkitx.nbt.tag.CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + tagBuilder.stringTag("Text", ""); + return tagBuilder.buildRootTag(); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SkullBlockEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SkullBlockEntityTranslator.java new file mode 100644 index 00000000..2380663b --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/block/entity/SkullBlockEntityTranslator.java @@ -0,0 +1,71 @@ +/* + * 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.block.entity; + +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.nbt.CompoundTagBuilder; +import com.nukkitx.nbt.tag.ByteTag; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.nbt.tag.FloatTag; +import com.nukkitx.nbt.tag.Tag; +import org.geysermc.connector.network.translators.block.BlockStateValues; + +import java.util.ArrayList; +import java.util.List; + +@BlockEntity(name = "Skull", delay = false, regex = "skull") +public class SkullBlockEntityTranslator extends BlockEntityTranslator implements RequiresBlockState { + + @Override + public boolean isBlock(BlockState blockState) { + return BlockStateValues.getSkullVariant(blockState) != -1; + } + + @Override + public List> translateTag(com.github.steveice10.opennbt.tag.builtin.CompoundTag tag, BlockState blockState) { + List> tags = new ArrayList<>(); + byte skullVariant = BlockStateValues.getSkullVariant(blockState); + float rotation = BlockStateValues.getSkullRotation(blockState) * 22.5f; + // Just in case... + if (skullVariant == -1) skullVariant = 0; + tags.add(new FloatTag("Rotation", rotation)); + tags.add(new ByteTag("SkullType", skullVariant)); + return tags; + } + + @Override + public com.github.steveice10.opennbt.tag.builtin.CompoundTag getDefaultJavaTag(String javaId, int x, int y, int z) { + return null; + } + + @Override + public CompoundTag getDefaultBedrockTag(String bedrockId, int x, int y, int z) { + CompoundTagBuilder tagBuilder = getConstantBedrockTag(bedrockId, x, y, z).toBuilder(); + tagBuilder.floatTag("Rotation", 0); + tagBuilder.byteTag("SkullType", (byte) 0); + return tagBuilder.buildRootTag(); + } +} 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 new file mode 100644 index 00000000..8df72d36 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/AnvilInventoryTranslator.java @@ -0,0 +1,146 @@ +/* + * 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.data.message.Message; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientRenameItemPacket; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import com.nukkitx.protocol.bedrock.data.ContainerId; +import com.nukkitx.protocol.bedrock.data.ContainerType; +import com.nukkitx.protocol.bedrock.data.InventoryActionData; +import com.nukkitx.protocol.bedrock.data.ItemData; +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; + +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.CURSOR) { + switch (action.getSlot()) { + case 1: + return 0; + case 2: + return 1; + case 50: + 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; + com.nukkitx.nbt.tag.CompoundTag tag = itemName.getTag(); + if (tag != null) { + rename = tag.getCompound("display").getString("Name"); + } else { + rename = ""; + } + ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename); + session.getDownstream().getSession().send(renameItemPacket); + } + if (anvilResult != null) { + //client will send another packet to grab anvil output + return; + } + + super.translateActions(session, inventory, actions); + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + if (slot >= 0 && slot <= 2) { + 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) { + String itemName = displayTag.get("Name").getValue().toString(); + Message message = Message.fromString(itemName); + rename = message.getText(); + } else { + rename = ""; + } + } else { + rename = ""; + } + ClientRenameItemPacket renameItemPacket = new ClientRenameItemPacket(rename); + session.getDownstream().getSession().send(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 new file mode 100644 index 00000000..5deb0370 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BaseInventoryTranslator.java @@ -0,0 +1,82 @@ +/* + * 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.ContainerId; +import com.nukkitx.protocol.bedrock.data.InventoryActionData; +import org.geysermc.connector.inventory.Inventory; +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{ + BaseInventoryTranslator(int size) { + super(size); + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + // + } + + @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; + } + } + return slotnum; + } + + @Override + public int javaSlotToBedrock(int slot) { + if (slot >= this.size) { + final int tmp = slot - this.size; + if (tmp < 27) { + return tmp + 9; + } else { + return tmp - 27; + } + } + return 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); + } +} 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 new file mode 100644 index 00000000..5f6274f0 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BlockInventoryTranslator.java @@ -0,0 +1,73 @@ +/* + * 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.world.block.BlockState; +import com.nukkitx.protocol.bedrock.data.ContainerType; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.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); + BlockState 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 new file mode 100644 index 00000000..bd143698 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/BrewingInventoryTranslator.java @@ -0,0 +1,99 @@ +/* + * 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.ContainerType; +import com.nukkitx.protocol.bedrock.data.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.getUpstream().sendPacket(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.getUpstream().sendPacket(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/CraftingInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.java new file mode 100644 index 00000000..92a1d90e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/CraftingInventoryTranslator.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; + +import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode; +import com.nukkitx.protocol.bedrock.data.ContainerId; +import com.nukkitx.protocol.bedrock.data.ContainerType; +import com.nukkitx.protocol.bedrock.data.InventoryActionData; +import com.nukkitx.protocol.bedrock.data.InventorySource; +import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; +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.network.translators.inventory.updater.InventoryUpdater; +import org.geysermc.connector.utils.InventoryUtils; + +import java.util.List; + +public class CraftingInventoryTranslator extends BaseInventoryTranslator { + private final InventoryUpdater updater; + + public CraftingInventoryTranslator() { + super(10); + this.updater = new CursorInventoryUpdater(); + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + // + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); + containerOpenPacket.setWindowId((byte) inventory.getId()); + containerOpenPacket.setType((byte) ContainerType.WORKBENCH.id()); + containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); + containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); + session.getUpstream().sendPacket(containerOpenPacket); + } + + @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); + } + + @Override + public int bedrockSlotToJava(InventoryActionData action) { + if (action.getSource().getContainerId() == ContainerId.CURSOR) { + int slotnum = action.getSlot(); + if (slotnum >= 32 && 42 >= slotnum) { + return slotnum - 31; + } else if (slotnum == 50) { + return 0; + } + } + return super.bedrockSlotToJava(action); + } + + @Override + public int javaSlotToBedrock(int slot) { + return slot == 0 ? 50 : slot + 31; + } + + @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/DoubleChestInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java new file mode 100644 index 00000000..c70a8995 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/DoubleChestInventoryTranslator.java @@ -0,0 +1,141 @@ +/* + * 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.Position; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.protocol.bedrock.data.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.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater; +import org.geysermc.connector.network.translators.inventory.updater.InventoryUpdater; + +public class DoubleChestInventoryTranslator extends BaseInventoryTranslator { + private final int blockId; + private final InventoryUpdater updater; + + public DoubleChestInventoryTranslator(int size) { + super(size); + BlockState javaBlockState = BlockTranslator.getJavaBlockState("minecraft:chest[facing=north,type=single,waterlogged=false]"); + this.blockId = BlockTranslator.getBedrockBlockId(javaBlockState); + this.updater = new ChestInventoryUpdater(54); + } + + @Override + public void prepareInventory(GeyserSession session, Inventory inventory) { + 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.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); + session.getUpstream().sendPacket(blockPacket); + + CompoundTag tag = CompoundTag.builder() + .stringTag("id", "Chest") + .intTag("x", position.getX()) + .intTag("y", position.getY()) + .intTag("z", position.getZ()) + .intTag("pairx", pairPosition.getX()) + .intTag("pairz", pairPosition.getZ()) + .stringTag("CustomName", inventory.getTitle()).buildRootTag(); + BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); + dataPacket.setData(tag); + dataPacket.setBlockPosition(position); + session.getUpstream().sendPacket(dataPacket); + + blockPacket = new UpdateBlockPacket(); + blockPacket.setDataLayer(0); + blockPacket.setBlockPosition(pairPosition); + blockPacket.setRuntimeId(blockId); + blockPacket.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); + session.getUpstream().sendPacket(blockPacket); + + tag = CompoundTag.builder() + .stringTag("id", "Chest") + .intTag("x", pairPosition.getX()) + .intTag("y", pairPosition.getY()) + .intTag("z", pairPosition.getZ()) + .intTag("pairx", position.getX()) + .intTag("pairz", position.getZ()) + .stringTag("CustomName", inventory.getTitle()).buildRootTag(); + dataPacket = new BlockEntityDataPacket(); + dataPacket.setData(tag); + dataPacket.setBlockPosition(pairPosition); + session.getUpstream().sendPacket(dataPacket); + + inventory.setHolderPosition(position); + } + + @Override + public void openInventory(GeyserSession session, Inventory inventory) { + ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); + containerOpenPacket.setWindowId((byte) inventory.getId()); + containerOpenPacket.setType((byte) ContainerType.CONTAINER.id()); + containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); + containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); + session.getUpstream().sendPacket(containerOpenPacket); + } + + @Override + public void closeInventory(GeyserSession session, Inventory inventory) { + Vector3i holderPos = inventory.getHolderPosition(); + Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); + BlockState realBlock = session.getChunkCache().getBlockAt(pos); + UpdateBlockPacket blockPacket = new UpdateBlockPacket(); + blockPacket.setDataLayer(0); + blockPacket.setBlockPosition(holderPos); + blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); + session.getUpstream().sendPacket(blockPacket); + + holderPos = holderPos.add(Vector3i.UNIT_X); + pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); + realBlock = session.getChunkCache().getBlockAt(pos); + blockPacket = new UpdateBlockPacket(); + blockPacket.setDataLayer(0); + blockPacket.setBlockPosition(holderPos); + blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); + session.getUpstream().sendPacket(blockPacket); + } + + @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/EnchantmentInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java new file mode 100644 index 00000000..ba7f8cc7 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/EnchantmentInventoryTranslator.java @@ -0,0 +1,42 @@ +/* + * 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.ContainerType; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.updater.ContainerInventoryUpdater; + +public class EnchantmentInventoryTranslator extends BlockInventoryTranslator { + public EnchantmentInventoryTranslator() { + super(2, "minecraft:enchanting_table", ContainerType.ENCHANTMENT, new ContainerInventoryUpdater()); + } + + @Override + public void updateProperty(GeyserSession session, Inventory inventory, int key, int value) { + + } +} 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 new file mode 100644 index 00000000..9b45201e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/FurnaceInventoryTranslator.java @@ -0,0 +1,70 @@ +/* + * 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.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.getUpstream().sendPacket(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 new file mode 100644 index 00000000..174cfbc1 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GrindstoneInventoryTranslator.java @@ -0,0 +1,68 @@ +/* + * 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.ContainerType; +import com.nukkitx.protocol.bedrock.data.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() == 124) { + 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 f17aba57..2a5afb8c 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,14 +25,25 @@ package org.geysermc.connector.network.translators.inventory; +import com.nukkitx.protocol.bedrock.data.InventoryActionData; +import lombok.AllArgsConstructor; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; +import java.util.List; + +@AllArgsConstructor public abstract class InventoryTranslator { + public final int size; public abstract void prepareInventory(GeyserSession session, Inventory inventory); public abstract void openInventory(GeyserSession session, Inventory inventory); + public abstract void closeInventory(GeyserSession session, Inventory inventory); + 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 SlotType getSlotType(int javaSlot); + public abstract void translateActions(GeyserSession session, Inventory inventory, List actions); } 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 new file mode 100644 index 00000000..440d01dc --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/PlayerInventoryTranslator.java @@ -0,0 +1,225 @@ +/* + * 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.data.game.entity.player.GameMode; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCreativeInventoryActionPacket; +import com.nukkitx.protocol.bedrock.data.*; +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.Translators; +import org.geysermc.connector.network.translators.inventory.action.InventoryActionDataTranslator; +import org.geysermc.connector.utils.InventoryUtils; + +import java.util.List; + +public class PlayerInventoryTranslator extends InventoryTranslator { + public PlayerInventoryTranslator() { + super(46); + } + + @Override + public void updateInventory(GeyserSession session, Inventory inventory) { + // Crafting grid + for (int i = 1; i < 5; i++) { + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.CURSOR); + slotPacket.setSlot(i + 27); + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(inventory.getItem(i))); + session.getUpstream().sendPacket(slotPacket); + } + + InventoryContentPacket inventoryContentPacket = new InventoryContentPacket(); + inventoryContentPacket.setContainerId(ContainerId.INVENTORY); + ItemData[] contents = new ItemData[36]; + // Inventory + for (int i = 9; i < 36; i++) { + contents[i] = Translators.getItemTranslator().translateToBedrock(inventory.getItem(i)); + } + // Hotbar + for (int i = 36; i < 45; i++) { + contents[i - 36] = Translators.getItemTranslator().translateToBedrock(inventory.getItem(i)); + } + inventoryContentPacket.setContents(contents); + session.getUpstream().sendPacket(inventoryContentPacket); + + // Armor + InventoryContentPacket armorContentPacket = new InventoryContentPacket(); + armorContentPacket.setContainerId(ContainerId.ARMOR); + contents = new ItemData[4]; + for (int i = 5; i < 9; i++) { + contents[i - 5] = Translators.getItemTranslator().translateToBedrock(inventory.getItem(i)); + } + armorContentPacket.setContents(contents); + session.getUpstream().sendPacket(armorContentPacket); + + // Offhand + InventoryContentPacket offhandPacket = new InventoryContentPacket(); + offhandPacket.setContainerId(ContainerId.OFFHAND); + offhandPacket.setContents(new ItemData[]{Translators.getItemTranslator().translateToBedrock(inventory.getItem(45))}); + session.getUpstream().sendPacket(offhandPacket); + } + + @Override + public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + if (slot >= 1 && slot <= 44) { + InventorySlotPacket slotPacket = new InventorySlotPacket(); + if (slot >= 9) { + slotPacket.setContainerId(ContainerId.INVENTORY); + if (slot >= 36) { + slotPacket.setSlot(slot - 36); + } else { + slotPacket.setSlot(slot); + } + } else if (slot >= 5) { + slotPacket.setContainerId(ContainerId.ARMOR); + slotPacket.setSlot(slot - 5); + } else { + slotPacket.setContainerId(ContainerId.CURSOR); + slotPacket.setSlot(slot + 27); + } + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(inventory.getItem(slot))); + session.getUpstream().sendPacket(slotPacket); + } else if (slot == 45) { + InventoryContentPacket offhandPacket = new InventoryContentPacket(); + offhandPacket.setContainerId(ContainerId.OFFHAND); + offhandPacket.setContents(new ItemData[]{Translators.getItemTranslator().translateToBedrock(inventory.getItem(slot))}); + session.getUpstream().sendPacket(offhandPacket); + } + } + + @Override + public int bedrockSlotToJava(InventoryActionData action) { + int slotnum = action.getSlot(); + switch (action.getSource().getContainerId()) { + case ContainerId.INVENTORY: + // Inventory + if (slotnum >= 9 && slotnum <= 35) { + return slotnum; + } + // Hotbar + if (slotnum >= 0 && slotnum <= 8) { + return slotnum + 36; + } + break; + case ContainerId.ARMOR: + if (slotnum >= 0 && slotnum <= 3) { + return slotnum + 5; + } + break; + case ContainerId.OFFHAND: + return 45; + case ContainerId.CURSOR: + if (slotnum >= 28 && 31 >= slotnum) { + return slotnum - 27; + } else if (slotnum == 50) { + return 0; + } + break; + } + return slotnum; + } + + @Override + public int javaSlotToBedrock(int slot) { + return 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) { + //crafting grid is not visible in creative mode in java edition + for (InventoryActionData action : actions) { + if (action.getSource().getContainerId() == ContainerId.CURSOR && (action.getSlot() >= 28 && 31 >= action.getSlot())) { + updateInventory(session, inventory); + InventoryUtils.updateCursor(session); + return; + } + } + + 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 = Translators.getItemTranslator().translateToJava(action.getToItem()); + } + ClientCreativeInventoryActionPacket creativePacket = new ClientCreativeInventoryActionPacket(javaSlot, javaItem); + session.getDownstream().getSession().send(creativePacket); + inventory.setItem(javaSlot, javaItem); + break; + case ContainerId.CURSOR: + if (action.getSlot() == 0) { + session.getInventory().setCursor(Translators.getItemTranslator().translateToJava(action.getToItem())); + } + break; + case ContainerId.NONE: + if (action.getSource().getType() == InventorySource.Type.WORLD_INTERACTION + && action.getSource().getFlag() == InventorySource.Flag.DROP_ITEM) { + javaItem = Translators.getItemTranslator().translateToJava(action.getToItem()); + ClientCreativeInventoryActionPacket creativeDropPacket = new ClientCreativeInventoryActionPacket(-1, javaItem); + session.getDownstream().getSession().send(creativeDropPacket); + } + break; + } + } + return; + } + + InventoryActionDataTranslator.translate(this, session, inventory, actions); + } + + @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) { + } +} 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/SingleChestInventoryTranslator.java new file mode 100644 index 00000000..5c99b012 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SingleChestInventoryTranslator.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; + +import com.nukkitx.protocol.bedrock.data.ContainerType; +import org.geysermc.connector.network.translators.inventory.updater.ChestInventoryUpdater; + +public class SingleChestInventoryTranslator extends BlockInventoryTranslator { + public SingleChestInventoryTranslator(int size) { + super(size, "minecraft:chest[facing=north,type=single,waterlogged=false]", ContainerType.CONTAINER, new ChestInventoryUpdater(27)); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SlotType.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SlotType.java new file mode 100644 index 00000000..045adbd3 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/SlotType.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; + +public enum SlotType { + NORMAL, + OUTPUT, + FURNACE_OUTPUT +} 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/action/Click.java new file mode 100644 index 00000000..1fdfa364 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/Click.java @@ -0,0 +1,38 @@ +/* + * 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.window.ClickItemParam; +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 final WindowActionParam actionParam; +} 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 new file mode 100644 index 00000000..cdc42f96 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/ClickPlan.java @@ -0,0 +1,125 @@ +/* + * 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.getDownstream().getSession().send(clickPacket); + session.getDownstream().getSession().send(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 new file mode 100644 index 00000000..750de5c8 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/action/InventoryActionDataTranslator.java @@ -0,0 +1,330 @@ +/* + * 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.ContainerId; +import com.nukkitx.protocol.bedrock.data.InventoryActionData; +import com.nukkitx.protocol.bedrock.data.InventorySource; +import com.nukkitx.protocol.bedrock.data.ItemData; +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.Translators; +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 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 || action.getSource().getContainerId() == ContainerId.CRAFTING_RESULT) { + return; + } else if (action.getSource().getType() == InventorySource.Type.WORLD_INTERACTION) { + worldAction = action; + } else if (action.getSource().getContainerId() == ContainerId.CURSOR && action.getSlot() == 0) { + cursorAction = action; + ItemData translatedCursor = Translators.getItemTranslator().translateToBedrock(session.getInventory().getCursor()); + if (!translatedCursor.equals(action.getFromItem())) { + refresh = true; + } + } else { + containerAction = action; + ItemData translatedItem = Translators.getItemTranslator().translateToBedrock(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.getDownstream().getSession().send(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.getDownstream().getSession().send(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.getDownstream().getSession().send(dropPacket); + } + } + ItemStack item = session.getInventory().getItem(javaSlot); + if (item != null) { + session.getInventory().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.getDownstream().getSession().send(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.getDownstream().getSession().send(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)); + if (cursorSlot != -1) { + plan.add(Click.LEFT, cursorSlot); + } else { + translator.updateInventory(session, inventory); + 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)); + if (cursorSlot != -1) { + plan.add(Click.LEFT, cursorSlot); + } else { + translator.updateInventory(session, inventory); + 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.getDownstream().getSession().send(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) { + /*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) { + 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/holder/BlockInventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java new file mode 100644 index 00000000..a9b0d4ad --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/BlockInventoryHolder.java @@ -0,0 +1,91 @@ +/* + * 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.holder; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.protocol.bedrock.data.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.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; + +@AllArgsConstructor +public class BlockInventoryHolder extends InventoryHolder { + private final int blockId; + private final ContainerType containerType; + + @Override + public void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + 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.getFlags().addAll(UpdateBlockPacket.FLAG_ALL_PRIORITY); + session.getUpstream().sendPacket(blockPacket); + inventory.setHolderPosition(position); + + CompoundTag tag = CompoundTag.builder() + .intTag("x", position.getX()) + .intTag("y", position.getY()) + .intTag("z", position.getZ()) + .stringTag("CustomName", inventory.getTitle()).buildRootTag(); + BlockEntityDataPacket dataPacket = new BlockEntityDataPacket(); + dataPacket.setData(tag); + dataPacket.setBlockPosition(position); + session.getUpstream().sendPacket(dataPacket); + } + + @Override + public void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); + containerOpenPacket.setWindowId((byte) inventory.getId()); + containerOpenPacket.setType((byte) containerType.id()); + containerOpenPacket.setBlockPosition(inventory.getHolderPosition()); + containerOpenPacket.setUniqueEntityId(inventory.getHolderId()); + session.getUpstream().sendPacket(containerOpenPacket); + } + + @Override + public void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + Vector3i holderPos = inventory.getHolderPosition(); + Position pos = new Position(holderPos.getX(), holderPos.getY(), holderPos.getZ()); + BlockState realBlock = session.getChunkCache().getBlockAt(pos); + UpdateBlockPacket blockPacket = new UpdateBlockPacket(); + blockPacket.setDataLayer(0); + blockPacket.setBlockPosition(holderPos); + blockPacket.setRuntimeId(BlockTranslator.getBedrockBlockId(realBlock)); + session.getUpstream().sendPacket(blockPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/InventoryHolder.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/InventoryHolder.java new file mode 100644 index 00000000..5a9e736e --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/holder/InventoryHolder.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.holder; + +import org.geysermc.connector.inventory.Inventory; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; + +public abstract class InventoryHolder { + public abstract void prepareInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory); + public abstract void openInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory); + public abstract void closeInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory); +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GenericInventoryTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java similarity index 64% rename from connector/src/main/java/org/geysermc/connector/network/translators/inventory/GenericInventoryTranslator.java rename to connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java index f7be13c3..dcffcd35 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/inventory/GenericInventoryTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ChestInventoryUpdater.java @@ -23,38 +23,32 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.connector.network.translators.inventory; +package org.geysermc.connector.network.translators.inventory.updater; -import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.data.ItemData; -import com.nukkitx.protocol.bedrock.packet.ContainerOpenPacket; import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; +import lombok.AllArgsConstructor; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -public class GenericInventoryTranslator extends InventoryTranslator { +@AllArgsConstructor +public class ChestInventoryUpdater extends InventoryUpdater { + private final int paddedSize; @Override - public void prepareInventory(GeyserSession session, Inventory inventory) { - // TODO: Add code here - } + public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + super.updateInventory(translator, session, inventory); - @Override - public void openInventory(GeyserSession session, Inventory inventory) { - ContainerOpenPacket containerOpenPacket = new ContainerOpenPacket(); - containerOpenPacket.setWindowId((byte) inventory.getId()); - containerOpenPacket.setType((byte) 0); - containerOpenPacket.setBlockPosition(Vector3i.ZERO); - session.getUpstream().sendPacket(containerOpenPacket); - } - - @Override - public void updateInventory(GeyserSession session, Inventory inventory) { - ItemData[] bedrockItems = new ItemData[inventory.getItems().length]; + ItemData[] bedrockItems = new ItemData[paddedSize]; for (int i = 0; i < bedrockItems.length; i++) { - bedrockItems[i] = Translators.getItemTranslator().translateToBedrock(inventory.getItems()[i]); + if (i <= translator.size) { + bedrockItems[i] = Translators.getItemTranslator().translateToBedrock(inventory.getItem(i)); + } else { + bedrockItems[i] = ItemData.AIR; + } } InventoryContentPacket contentPacket = new InventoryContentPacket(); @@ -64,11 +58,15 @@ public class GenericInventoryTranslator extends InventoryTranslator { } @Override - public void updateSlot(GeyserSession session, Inventory inventory, int slot) { + 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(inventory.getId()); - slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(inventory.getItems()[slot])); - slotPacket.setSlot(slot); + slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(inventory.getItem(javaSlot))); session.getUpstream().sendPacket(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 new file mode 100644 index 00000000..812ce8f8 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/ContainerInventoryUpdater.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.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.Translators; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; + +public class ContainerInventoryUpdater extends InventoryUpdater { + @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)] = Translators.getItemTranslator().translateToBedrock(inventory.getItem(i)); + } + + InventoryContentPacket contentPacket = new InventoryContentPacket(); + contentPacket.setContainerId(inventory.getId()); + contentPacket.setContents(bedrockItems); + session.getUpstream().sendPacket(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(inventory.getId()); + slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(inventory.getItem(javaSlot))); + session.getUpstream().sendPacket(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/CursorInventoryUpdater.java new file mode 100644 index 00000000..f0c1c760 --- /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.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.Translators; +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.CURSOR); + slotPacket.setSlot(bedrockSlot); + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(inventory.getItem(i))); + session.getUpstream().sendPacket(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.CURSOR); + slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(inventory.getItem(javaSlot))); + session.getUpstream().sendPacket(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 new file mode 100644 index 00000000..32005685 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/inventory/updater/InventoryUpdater.java @@ -0,0 +1,61 @@ +/* + * 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.ContainerId; +import com.nukkitx.protocol.bedrock.data.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.Translators; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; + +public abstract class InventoryUpdater { + public void updateInventory(InventoryTranslator translator, GeyserSession session, Inventory inventory) { + ItemData[] bedrockItems = new ItemData[36]; + for (int i = 0; i < 36; i++) { + final int offset = i < 9 ? 27 : -9; + bedrockItems[i] = Translators.getItemTranslator().translateToBedrock(inventory.getItem(translator.size + i + offset)); + } + InventoryContentPacket contentPacket = new InventoryContentPacket(); + contentPacket.setContainerId(ContainerId.INVENTORY); + contentPacket.setContents(bedrockItems); + session.getUpstream().sendPacket(contentPacket); + } + + public boolean updateSlot(InventoryTranslator translator, GeyserSession session, Inventory inventory, int javaSlot) { + if (javaSlot >= translator.size) { + InventorySlotPacket slotPacket = new InventorySlotPacket(); + slotPacket.setContainerId(ContainerId.INVENTORY); + slotPacket.setSlot(translator.javaSlotToBedrock(javaSlot)); + slotPacket.setItem(Translators.getItemTranslator().translateToBedrock(inventory.getItem(javaSlot))); + session.getUpstream().sendPacket(slotPacket); + return true; + } + return false; + } +} 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 new file mode 100644 index 00000000..c5c152a2 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/Enchantment.java @@ -0,0 +1,92 @@ +/* + * 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.item; + +import lombok.Getter; + +import java.util.Locale; + +@Getter +enum Enchantment { + PROTECTION, + FIRE_PROTECTION, + FEATHER_FALLING, + BLAST_PROTECTION, + PROJECTILE_PROTECTION, + THORNS, + RESPIRATION, + DEPTH_STRIDER, + AQUA_AFFINITY, + SHARPNESS, + SMITE, + BANE_OF_ARTHROPODS, + KNOCKBACK, + FIRE_ASPECT, + LOOTING, + EFFICIENCY, + SILK_TOUCH, + UNBREAKING, + FORTUNE, + POWER, + PUNCH, + FLAME, + INFINITY, + LUCK_OF_THE_SEA, + LURE, + FROST_WALKER, + MENDING, + BINDING_CURSE, + VANISHING_CURSE, + IMPALING, + RIPTIDE, + LOYALTY, + CHANNELING, + MULTISHOT, + PIERCING, + QUICK_CHARGE; + + private final String javaIdentifier; + + Enchantment() { + this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ENGLISH); + } + + public static Enchantment getByJavaIdentifier(String javaIdentifier) { + for (Enchantment enchantment : Enchantment.values()) { + if (enchantment.javaIdentifier.equals(javaIdentifier)) { + return enchantment; + } + } + return null; + } + + public static Enchantment getByBedrockId(int bedrockId) { + if (bedrockId >= 0 && bedrockId < Enchantment.values().length) { + return Enchantment.values()[bedrockId]; + } + return null; + } +} 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 fd4f0b02..e579c20e 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,11 +34,11 @@ public class ItemEntry { public static ItemEntry AIR = new ItemEntry("minecraft:air", 0, 0, 0); - private String javaIdentifier; - private int javaId; + private final String javaIdentifier; + private final int javaId; - private int bedrockId; - private int bedrockData; + private final int bedrockId; + private final int bedrockData; @Override public boolean equals(Object obj) { 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 68f88bb2..0bcbf4b7 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 @@ -43,6 +43,7 @@ import com.github.steveice10.opennbt.tag.builtin.Tag; import com.nukkitx.protocol.bedrock.data.ItemData; import org.geysermc.connector.GeyserConnector; + import org.geysermc.connector.utils.MessageUtils; import org.geysermc.connector.utils.Toolbox; @@ -53,24 +54,45 @@ import java.util.Map; public class ItemTranslator { + private Map javaIdentifierMap = new HashMap<>(); + public ItemStack translateToJava(ItemData data) { ItemEntry javaItem = getItem(data); if (data.getTag() == null) { return new ItemStack(javaItem.getJavaId(), data.getCount()); + } else if (javaItem.getJavaIdentifier().equals("minecraft:enchanted_book")) { + CompoundTag javaTag = translateToJavaNBT(data.getTag()); + Map javaValue = javaTag.getValue(); + Tag enchTag = javaValue.get("Enchantments"); + if (enchTag instanceof ListTag) { + enchTag = new ListTag("StoredEnchantments", ((ListTag) enchTag).getValue()); + javaValue.remove("Enchantments"); + javaValue.put("StoredEnchantments", enchTag); + javaTag.setValue(javaValue); + } + return new ItemStack(javaItem.getJavaId(), data.getCount(), javaTag); } return new ItemStack(javaItem.getJavaId(), data.getCount(), translateToJavaNBT(data.getTag())); } public ItemData translateToBedrock(ItemStack stack) { - // Most likely dirt if null if (stack == null) { - return ItemData.of(3, (short)0, 0); + return ItemData.AIR; } ItemEntry bedrockItem = getItem(stack); if (stack.getNbt() == null) { return ItemData.of(bedrockItem.getBedrockId(), (short) bedrockItem.getBedrockData(), stack.getAmount()); + } else if (bedrockItem.getJavaIdentifier().endsWith("potion")) { + Tag potionTag = stack.getNbt().get("Potion"); + if (potionTag instanceof StringTag) { + Potion potion = Potion.getByJavaIdentifier(((StringTag) potionTag).getValue()); + if (potion != null) { + return ItemData.of(bedrockItem.getBedrockId(), potion.getBedrockId(), stack.getAmount(), translateToBedrockNBT(stack.getNbt())); + } + GeyserConnector.getInstance().getLogger().debug("Unknown java potion: " + potionTag.getValue()); + } } // TODO: Create proper transformers instead of shoving everything here @@ -92,7 +114,14 @@ public class ItemTranslator { public ItemEntry getItem(ItemData data) { for (ItemEntry itemEntry : Toolbox.ITEM_ENTRIES.values()) { - if (itemEntry.getBedrockId() == data.getId() && itemEntry.getBedrockData() == data.getDamage()) { + if (itemEntry.getBedrockId() == data.getId() && (itemEntry.getBedrockData() == data.getDamage() || itemEntry.getJavaIdentifier().endsWith("potion"))) { + return itemEntry; + } + } + // If item find was unsuccessful first time, we try again while ignoring damage + // Fixes piston, sticky pistons, dispensers and droppers turning into air from creative inventory + for (ItemEntry itemEntry : Toolbox.ITEM_ENTRIES.values()) { + if (itemEntry.getBedrockId() == data.getId()) { return itemEntry; } } @@ -101,6 +130,11 @@ public class ItemTranslator { return ItemEntry.AIR; } + public ItemEntry getItemEntry(String javaIdentifier) { + return javaIdentifierMap.computeIfAbsent(javaIdentifier, key -> Toolbox.ITEM_ENTRIES.values() + .stream().filter(itemEntry -> itemEntry.getJavaIdentifier().equals(key)).findFirst().orElse(null)); + } + private CompoundTag translateToJavaNBT(com.nukkitx.nbt.tag.CompoundTag tag) { CompoundTag javaTag = new CompoundTag(tag.getName()); Map javaValue = javaTag.getValue(); @@ -111,10 +145,11 @@ public class ItemTranslator { if (translatedTag == null) continue; - javaValue.put(str, translatedTag); + javaValue.put(translatedTag.getName(), translatedTag); } } + javaTag.setValue(javaValue); return javaTag; } @@ -173,6 +208,29 @@ public class ItemTranslator { com.nukkitx.nbt.tag.ListTag listTag = (com.nukkitx.nbt.tag.ListTag) tag; List tags = new ArrayList<>(); + + if (tag.getName().equals("ench")) { + for (Object value : listTag.getValue()) { + if (!(value instanceof com.nukkitx.nbt.tag.CompoundTag)) + continue; + + com.nukkitx.nbt.tag.CompoundTag tagValue = (com.nukkitx.nbt.tag.CompoundTag) value; + int bedrockId = tagValue.getShort("id", (short) -1); + Enchantment enchantment = Enchantment.getByBedrockId(bedrockId); + if (enchantment != null) { + CompoundTag javaTag = new CompoundTag(""); + Map javaValue = javaTag.getValue(); + javaValue.put("id", new StringTag("id", enchantment.getJavaIdentifier())); + javaValue.put("lvl", new IntTag("lvl", tagValue.getShort("lvl", (short) 1))); + javaTag.setValue(javaValue); + tags.add(javaTag); + } else { + GeyserConnector.getInstance().getLogger().debug("Unknown bedrock enchantment: " + bedrockId); + } + } + return new ListTag("Enchantments", tags); + } + for (Object value : listTag.getValue()) { if (!(value instanceof com.nukkitx.nbt.tag.Tag)) continue; @@ -201,7 +259,7 @@ public class ItemTranslator { if (translatedTag == null) continue; - javaValue.put(str, translatedTag); + javaValue.put(translatedTag.getName(), translatedTag); } } @@ -262,7 +320,33 @@ public class ItemTranslator { if (tag instanceof ListTag) { ListTag listTag = (ListTag) tag; - if (listTag.getName().equalsIgnoreCase("Lore")) { + if (listTag.getName().equalsIgnoreCase("Enchantments") || listTag.getName().equalsIgnoreCase("StoredEnchantments")) { + List tags = new ArrayList<>(); + for (Object value : listTag.getValue()) { + if (!(value instanceof CompoundTag)) + continue; + + Tag javaEnchLvl = ((CompoundTag) value).get("lvl"); + if (!(javaEnchLvl instanceof ShortTag)) + continue; + + Tag javaEnchId = ((CompoundTag) value).get("id"); + if (!(javaEnchId instanceof StringTag)) + continue; + + Enchantment enchantment = Enchantment.getByJavaIdentifier(((StringTag) javaEnchId).getValue()); + if (enchantment == null) { + GeyserConnector.getInstance().getLogger().debug("Unknown java enchantment: " + javaEnchId.getValue()); + continue; + } + + com.nukkitx.nbt.CompoundTagBuilder builder = com.nukkitx.nbt.tag.CompoundTag.EMPTY.toBuilder(); + builder.shortTag("lvl", ((ShortTag) javaEnchLvl).getValue()); + builder.shortTag("id", (short) enchantment.ordinal()); + tags.add(builder.buildRootTag()); + } + return new com.nukkitx.nbt.tag.ListTag<>("ench", com.nukkitx.nbt.tag.CompoundTag.class, tags); + } else if (listTag.getName().equalsIgnoreCase("Lore")) { List tags = new ArrayList<>(); for (Object value : listTag.getValue()) { if (!(value instanceof Tag)) diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java b/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java new file mode 100644 index 00000000..f711d3ea --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/Potion.java @@ -0,0 +1,102 @@ +/* + * 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.item; + +import lombok.Getter; + +import java.util.Locale; + +@Getter +enum Potion { + WATER(0), + MUNDANE(1), + THICK(3), + AWKWARD(4), + NIGHT_VISION(5), + LONG_NIGHT_VISION(6), + INVISIBILITY(7), + LONG_INVISIBILITY(8), + LEAPING(9), + STRONG_LEAPING(11), + LONG_LEAPING(10), + FIRE_RESISTANCE(12), + LONG_FIRE_RESISTANCE(13), + SWIFTNESS(14), + STRONG_SWIFTNESS(16), + LONG_SWIFTNESS(15), + SLOWNESS(17), + STRONG_SLOWNESS(18), //does not exist + LONG_SLOWNESS(18), + WATER_BREATHING(19), + LONG_WATER_BREATHING(20), + HEALING(21), + STRONG_HEALING(22), + HARMING(23), + STRONG_HARMING(24), + POISON(25), + STRONG_POISON(27), + LONG_POISON(26), + REGENERATION(28), + STRONG_REGENERATION(30), + LONG_REGENERATION(29), + STRENGTH(31), + STRONG_STRENGTH(33), + LONG_STRENGTH(32), + WEAKNESS(34), + LONG_WEAKNESS(35), + LUCK(2), //does not exist + TURTLE_MASTER(37), + STRONG_TURTLE_MASTER(39), + LONG_TURTLE_MASTER(38), + SLOW_FALLING(40), + LONG_SLOW_FALLING(41); + + private final String javaIdentifier; + private final short bedrockId; + + Potion(int bedrockId) { + this.javaIdentifier = "minecraft:" + this.name().toLowerCase(Locale.ENGLISH); + this.bedrockId = (short) bedrockId; + } + + public static Potion getByJavaIdentifier(String javaIdentifier) { + for (Potion potion : Potion.values()) { + if (potion.javaIdentifier.equals(javaIdentifier)) { + return potion; + } + } + return null; + } + + public static Potion getByBedrockId(short bedrockId) { + for (Potion potion : Potion.values()) { + if (potion.bedrockId == bedrockId) { + return potion; + } + } + return null; + } +} 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 new file mode 100644 index 00000000..5d1ddd26 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/item/ToolItemEntry.java @@ -0,0 +1,15 @@ +package org.geysermc.connector.network.translators.item; + +import lombok.Getter; + +@Getter +public class ToolItemEntry extends ItemEntry { + private final String toolType; + private final String toolTier; + + public ToolItemEntry(String javaIdentifier, int javaId, int bedrockId, int bedrockData, String toolType, String toolTier) { + super(javaIdentifier, javaId, bedrockId, bedrockData); + this.toolType = toolType; + this.toolTier = toolTier; + } +} 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 new file mode 100644 index 00000000..18b155e0 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/JavaDeclareRecipesTranslator.java @@ -0,0 +1,174 @@ +/* + * 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; + +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.packet.ingame.server.ServerDeclareRecipesPacket; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.protocol.bedrock.data.CraftingData; +import com.nukkitx.protocol.bedrock.data.ItemData; +import com.nukkitx.protocol.bedrock.data.PotionMixData; +import com.nukkitx.protocol.bedrock.packet.CraftingDataPacket; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +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.Translators; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.utils.Toolbox; + +import java.util.*; +import java.util.stream.Collectors; + +@Translator(packet = ServerDeclareRecipesPacket.class) +public class JavaDeclareRecipesTranslator extends PacketTranslator { + private static final Collection POTION_MIXES = + Arrays.stream(new int[]{372, 331, 348, 376, 289, 437, 353, 414, 382, 375, 462, 378, 396, 377, 370, 469, 470}) + .mapToObj(ingredient -> new PotionMixData(0, ingredient, 0)) + .collect(Collectors.toList()); + + @Override + public void translate(ServerDeclareRecipesPacket packet, GeyserSession session) { + CraftingDataPacket craftingDataPacket = new CraftingDataPacket(); + craftingDataPacket.setCleanRecipes(true); + for (Recipe recipe : packet.getRecipes()) { + switch (recipe.getType()) { + case CRAFTING_SHAPELESS: { + ShapelessRecipeData shapelessRecipeData = (ShapelessRecipeData) recipe.getData(); + ItemData output = Translators.getItemTranslator().translateToBedrock(shapelessRecipeData.getResult()); + output = ItemData.of(output.getId(), output.getDamage(), output.getCount()); //strip NBT + ItemData[][] inputCombinations = combinations(shapelessRecipeData.getIngredients()); + for (ItemData[] inputs : inputCombinations) { + UUID uuid = UUID.randomUUID(); + craftingDataPacket.getCraftingData().add(CraftingData.fromShapeless(uuid.toString(), + inputs, new ItemData[]{output}, uuid, "crafting_table", 0)); + } + break; + } + case CRAFTING_SHAPED: { + ShapedRecipeData shapedRecipeData = (ShapedRecipeData) recipe.getData(); + ItemData output = Translators.getItemTranslator().translateToBedrock(shapedRecipeData.getResult()); + output = ItemData.of(output.getId(), output.getDamage(), output.getCount()); //strip NBT + ItemData[][] inputCombinations = combinations(shapedRecipeData.getIngredients()); + for (ItemData[] inputs : inputCombinations) { + UUID uuid = UUID.randomUUID(); + craftingDataPacket.getCraftingData().add(CraftingData.fromShaped(uuid.toString(), + shapedRecipeData.getWidth(), shapedRecipeData.getHeight(), inputs, + new ItemData[]{output}, uuid, "crafting_table", 0)); + } + break; + } + } + } + craftingDataPacket.getPotionMixData().addAll(POTION_MIXES); + session.getUpstream().sendPacket(craftingDataPacket); + } + + //TODO: rewrite + private ItemData[][] combinations(Ingredient[] ingredients) { + Map, IntSet> squashedOptions = new HashMap<>(); + for (int i = 0; i < ingredients.length; i++) { + if (ingredients[i].getOptions().length == 0) { + squashedOptions.computeIfAbsent(Collections.singleton(ItemData.AIR), k -> new IntOpenHashSet()).add(i); + continue; + } + Ingredient ingredient = ingredients[i]; + Map> groupedByIds = Arrays.stream(ingredient.getOptions()) + .map(item -> Translators.getItemTranslator().translateToBedrock(item)) + .collect(Collectors.groupingBy(item -> new GroupedItem(item.getId(), item.getCount(), item.getTag()))); + Set optionSet = new HashSet<>(groupedByIds.size()); + for (Map.Entry> entry : groupedByIds.entrySet()) { + if (entry.getValue().size() > 1) { + GroupedItem groupedItem = entry.getKey(); + int idCount = 0; + //not optimal + for (ItemEntry itemEntry : Toolbox.ITEM_ENTRIES.values()) { + if (itemEntry.getBedrockId() == groupedItem.id) { + idCount++; + } + } + if (entry.getValue().size() < idCount) { + optionSet.addAll(entry.getValue()); + } else { + optionSet.add(ItemData.of(groupedItem.id, (short) -1, groupedItem.count, groupedItem.tag)); + } + } else { + ItemData item = entry.getValue().get(0); + optionSet.add(item); + } + } + squashedOptions.computeIfAbsent(optionSet, k -> new IntOpenHashSet()).add(i); + } + int totalCombinations = 1; + for (Set optionSet : squashedOptions.keySet()) { + totalCombinations *= optionSet.size(); + } + if (totalCombinations > 500) { + ItemData[] translatedItems = new ItemData[ingredients.length]; + for (int i = 0; i < ingredients.length; i++) { + if (ingredients[i].getOptions().length > 0) { + translatedItems[i] = Translators.getItemTranslator().translateToBedrock(ingredients[i].getOptions()[0]); + } else { + translatedItems[i] = ItemData.AIR; + } + } + return new ItemData[][]{translatedItems}; + } + List> sortedSets = new ArrayList<>(squashedOptions.keySet()); + sortedSets.sort(Comparator.comparing(Set::size, Comparator.reverseOrder())); + ItemData[][] combinations = new ItemData[totalCombinations][ingredients.length]; + int x = 1; + for (Set set : sortedSets) { + IntSet slotSet = squashedOptions.get(set); + int i = 0; + for (ItemData item : set) { + for (int j = 0; j < totalCombinations / set.size(); j++) { + final int comboIndex = (i * x) + (j % x) + ((j / x) * set.size() * x); + for (int slot : slotSet) { + combinations[comboIndex][slot] = item; + } + } + i++; + } + x *= set.size(); + } + return combinations; + } + + @EqualsAndHashCode + @AllArgsConstructor + private static class GroupedItem { + int id; + int count; + CompoundTag tag; + } +} 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 b28e9010..cc2c540f 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 @@ -53,6 +53,8 @@ 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.getInventoryCache().setOpenInventory(null); + SetPlayerGameTypePacket playerGameTypePacket = new SetPlayerGameTypePacket(); playerGameTypePacket.setGamemode(packet.getGamemode().ordinal()); session.getUpstream().sendPacket(playerGameTypePacket); diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java index be54e967..88e0969e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/JavaEntityEffectTranslator.java @@ -26,6 +26,7 @@ package org.geysermc.connector.network.translators.java.entity; import org.geysermc.connector.entity.Entity; +import org.geysermc.connector.entity.PlayerEntity; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; @@ -42,6 +43,7 @@ public class JavaEntityEffectTranslator extends PacketTranslator { @Override public void translate(ServerPlayerActionAckPacket packet, GeyserSession session) { + LevelEventPacket levelEvent = new LevelEventPacket(); switch (packet.getAction()) { case FINISH_DIGGING: ChunkUtils.updateBlock(session, packet.getNewState(), packet.getPosition()); break; + + case START_DIGGING: + levelEvent.setType(LevelEventType.BLOCK_START_BREAK); + levelEvent.setPosition(Vector3f.from( + packet.getPosition().getX(), + packet.getPosition().getY(), + packet.getPosition().getZ() + )); + double blockHardness = BlockTranslator.JAVA_RUNTIME_ID_TO_HARDNESS.get(packet.getNewState().getId()); + PlayerInventory inventory = session.getInventory(); + ItemStack item = inventory.getItemInHand(); + ItemEntry itemEntry = null; + CompoundTag nbtData = new CompoundTag(""); + if (item != null) { + itemEntry = Translators.getItemTranslator().getItem(item); + nbtData = item.getNbt(); + } + double breakTime = Math.ceil(BlockUtils.getBreakTime(blockHardness, packet.getNewState().getId(), itemEntry, nbtData, session.getPlayerEntity()) * 20); + levelEvent.setData((int) (65535 / breakTime)); + session.getUpstream().sendPacket(levelEvent); + break; + + case CANCEL_DIGGING: + levelEvent.setType(LevelEventType.BLOCK_STOP_BREAK); + levelEvent.setPosition(Vector3f.from( + packet.getPosition().getX(), + packet.getPosition().getY(), + packet.getPosition().getZ() + )); + levelEvent.setData(0); + session.getUpstream().sendPacket(levelEvent); + break; } } -} +} \ No newline at end of file diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerChangeHeldItemTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerChangeHeldItemTranslator.java new file mode 100644 index 00000000..c4c1a3af --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/player/JavaPlayerChangeHeldItemTranslator.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.java.entity.player; + +import com.github.steveice10.mc.protocol.packet.ingame.server.entity.player.ServerPlayerChangeHeldItemPacket; +import com.nukkitx.protocol.bedrock.packet.PlayerHotbarPacket; +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = ServerPlayerChangeHeldItemPacket.class) +public class JavaPlayerChangeHeldItemTranslator extends PacketTranslator { + + @Override + public void translate(ServerPlayerChangeHeldItemPacket packet, GeyserSession session) { + PlayerHotbarPacket hotbarPacket = new PlayerHotbarPacket(); + hotbarPacket.setContainerId(0); + hotbarPacket.setSelectedHotbarSlot(packet.getSlot()); + hotbarPacket.setSelectHotbarSlot(true); + session.getUpstream().sendPacket(hotbarPacket); + + session.getInventory().setHeldItemSlot(packet.getSlot()); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/inventory/OpenWindowPacketTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/inventory/OpenWindowPacketTranslator.java deleted file mode 100644 index 2719a72a..00000000 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/inventory/OpenWindowPacketTranslator.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.geysermc.connector.network.translators.java.inventory; - -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.Translators; -import org.geysermc.connector.network.translators.inventory.InventoryTranslator; - -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; - -@Translator(packet = ServerOpenWindowPacket.class) -public class OpenWindowPacketTranslator extends PacketTranslator { - @Override - public void translate(ServerOpenWindowPacket packet, GeyserSession session) { - System.out.println("debug: " + packet.getType()); - InventoryTranslator translator = Translators.getInventoryTranslator(); - - translator.openInventory(session, new Inventory(packet.getName(), packet.getWindowId(), packet.getType(), 54)); - - } -} 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 new file mode 100644 index 00000000..8162b82a --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaCloseWindowTranslator.java @@ -0,0 +1,45 @@ +/* + * 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.ServerCloseWindowPacket; +import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket; +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.utils.InventoryUtils; + +@Translator(packet = ServerCloseWindowPacket.class) +public class JavaCloseWindowTranslator extends PacketTranslator { + + @Override + public void translate(ServerCloseWindowPacket packet, GeyserSession session) { + ContainerClosePacket closePacket = new ContainerClosePacket(); + closePacket.setWindowId((byte)packet.getWindowId()); + session.getUpstream().sendPacket(closePacket); + 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 new file mode 100644 index 00000000..9ecf4559 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaConfirmTransactionTranslator.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.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; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; +import org.geysermc.connector.utils.InventoryUtils; + +@Translator(packet = ServerConfirmTransactionPacket.class) +public class JavaConfirmTransactionTranslator extends PacketTranslator { + + @Override + public void translate(ServerConfirmTransactionPacket packet, GeyserSession session) { + if (!packet.isAccepted()) { + ClientConfirmTransactionPacket confirmPacket = new ClientConfirmTransactionPacket(packet.getWindowId(), packet.getActionId(), true); + session.getDownstream().getSession().send(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 9495bed4..38276193 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 @@ -25,18 +25,68 @@ package org.geysermc.connector.network.translators.java.window; +import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; +import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.nukkitx.protocol.bedrock.packet.ContainerClosePacket; +import org.geysermc.connector.GeyserConnector; +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.Translators; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.utils.InventoryUtils; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; +import java.util.concurrent.TimeUnit; @Translator(packet = ServerOpenWindowPacket.class) public class JavaOpenWindowTranslator extends PacketTranslator { @Override public void translate(ServerOpenWindowPacket packet, GeyserSession session) { - InventoryUtils.openInventory(session, packet); + if (packet.getWindowId() == 0) { + return; + } + InventoryTranslator newTranslator = Translators.getInventoryTranslators().get(packet.getType()); + Inventory openInventory = session.getInventoryCache().getOpenInventory(); + if (newTranslator == null) { + if (openInventory != null) { + ContainerClosePacket closePacket = new ContainerClosePacket(); + closePacket.setWindowId((byte)openInventory.getId()); + session.getUpstream().sendPacket(closePacket); + Translators.getInventoryTranslators().get(openInventory.getWindowType()).closeInventory(session, openInventory); + } + ClientCloseWindowPacket closeWindowPacket = new ClientCloseWindowPacket(packet.getWindowId()); + session.getDownstream().getSession().send(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()); + } + + Inventory newInventory = new Inventory(name, packet.getWindowId(), packet.getType(), newTranslator.size + 36); + session.getInventoryCache().cacheInventory(newInventory); + if (openInventory != null) { + InventoryTranslator openTranslator = Translators.getInventoryTranslators().get(openInventory.getWindowType()); + if (!openTranslator.getClass().equals(newTranslator.getClass())) { + InventoryUtils.closeInventory(session, openInventory.getId()); + GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> InventoryUtils.openInventory(session, newInventory), 500, TimeUnit.MILLISECONDS); + return; + } + } + + 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 93df65e8..6fafa2a4 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 @@ -25,47 +25,41 @@ package org.geysermc.connector.network.translators.java.window; +import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerSetSlotPacket; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.InventoryCache; import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.InventoryUtils; -import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerSetSlotPacket; +import java.util.Objects; @Translator(packet = ServerSetSlotPacket.class) public class JavaSetSlotTranslator extends PacketTranslator { @Override public void translate(ServerSetSlotPacket packet, GeyserSession session) { - InventoryCache inventoryCache = session.getInventoryCache(); - if (!inventoryCache.getInventories().containsKey(packet.getWindowId())) { - inventoryCache.cachePacket(packet.getWindowId(), packet); + if (packet.getWindowId() == 255 && packet.getSlot() == -1) { //cursor + if (Objects.equals(session.getInventory().getCursor(), packet.getItem())) + return; + if (session.getCraftSlot() != 0) + return; + + session.getInventory().setCursor(packet.getItem()); + InventoryUtils.updateCursor(session); return; } - Inventory inventory = inventoryCache.getInventories().get(packet.getWindowId()); - if (packet.getWindowId() != 0 && inventory.getWindowType() == null) + Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId()); + if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null)) return; - // Player inventory - if (packet.getWindowId() == 0) { - if (packet.getSlot() >= inventory.getItems().length) - return; // Most likely not a player inventory - - ItemStack[] items = inventory.getItems(); - items[packet.getSlot()] = packet.getItem(); - inventory.setItems(items); - - InventoryUtils.refreshPlayerInventory(session, inventory); - - if (inventory.isOpen()) { - InventoryUtils.updateSlot(session, packet); - } else { - inventoryCache.cachePacket(packet.getWindowId(), packet); - } + InventoryTranslator translator = Translators.getInventoryTranslators().get(inventory.getWindowType()); + if (translator != null) { + inventory.setItem(packet.getSlot(), packet.getItem()); + 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 f2287f6d..eab57a64 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 @@ -25,34 +25,34 @@ package org.geysermc.connector.network.translators.java.window; +import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerWindowItemsPacket; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; -import org.geysermc.connector.network.session.cache.InventoryCache; import org.geysermc.connector.network.translators.PacketTranslator; import org.geysermc.connector.network.translators.Translator; -import org.geysermc.connector.utils.InventoryUtils; +import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerWindowItemsPacket; +import java.util.Arrays; @Translator(packet = ServerWindowItemsPacket.class) public class JavaWindowItemsTranslator extends PacketTranslator { @Override public void translate(ServerWindowItemsPacket packet, GeyserSession session) { - InventoryCache inventoryCache = session.getInventoryCache(); - if (!inventoryCache.getInventories().containsKey(packet.getWindowId())) { - inventoryCache.cachePacket(packet.getWindowId(), packet); + Inventory inventory = session.getInventoryCache().getInventories().get(packet.getWindowId()); + if (inventory == null || (packet.getWindowId() != 0 && inventory.getWindowType() == null)) return; - } - Inventory inventory = inventoryCache.getInventories().get(packet.getWindowId()); - // Player inventory - if (packet.getWindowId() == 0) { + if (packet.getItems().length < inventory.getSize()) { + inventory.setItems(Arrays.copyOf(packet.getItems(), inventory.getSize())); + } else { inventory.setItems(packet.getItems()); - InventoryUtils.refreshPlayerInventory(session, inventory); - return; } - InventoryUtils.updateInventory(session, packet); + InventoryTranslator translator = Translators.getInventoryTranslators().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 new file mode 100644 index 00000000..827da7b7 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/window/JavaWindowPropertyTranslator.java @@ -0,0 +1,50 @@ +/* + * 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.ServerWindowPropertyPacket; +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.Translators; +import org.geysermc.connector.network.translators.inventory.InventoryTranslator; + +@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; + + InventoryTranslator translator = Translators.getInventoryTranslators().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/JavaBlockValueTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java new file mode 100644 index 00000000..f4a4d9ef --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaBlockValueTranslator.java @@ -0,0 +1,57 @@ +/* + * 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.world; + +import com.github.steveice10.mc.protocol.data.game.world.block.value.ChestValue; +import com.github.steveice10.mc.protocol.data.game.world.block.value.EndGatewayValue; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerBlockValuePacket; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.packet.BlockEventPacket; + +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.PacketTranslator; +import org.geysermc.connector.network.translators.Translator; + +@Translator(packet = ServerBlockValuePacket.class) +public class JavaBlockValueTranslator extends PacketTranslator { + + @Override + public void translate(ServerBlockValuePacket packet, GeyserSession session) { + BlockEventPacket blockEventPacket = new BlockEventPacket(); + blockEventPacket.setBlockPosition(Vector3i.from(packet.getPosition().getX(), + packet.getPosition().getY(), packet.getPosition().getZ())); + if (packet.getValue() instanceof ChestValue) { + ChestValue value = (ChestValue) packet.getValue() ; + blockEventPacket.setEventType(1); + blockEventPacket.setEventData(value.getViewers() > 0 ? 1 : 0); + session.getUpstream().sendPacket(blockEventPacket); + } + if (packet.getValue() instanceof EndGatewayValue) { + blockEventPacket.setEventType(1); + session.getUpstream().sendPacket(blockEventPacket); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java index bb73c5f0..e72038c5 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaChunkDataTranslator.java @@ -25,6 +25,19 @@ package org.geysermc.connector.network.translators.java.world; +import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; +import com.nukkitx.nbt.NbtUtils; +import com.nukkitx.nbt.stream.NBTOutputStream; +import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.network.VarInts; +import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.objects.Object2IntMap; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.BiomeTranslator; @@ -33,14 +46,7 @@ import org.geysermc.connector.network.translators.Translator; import org.geysermc.connector.utils.ChunkUtils; import org.geysermc.connector.world.chunk.ChunkSection; -import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; -import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; -import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerChunkDataPacket; -import com.nukkitx.network.VarInts; -import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; +import java.util.Map; @Translator(packet = ServerChunkDataPacket.class) public class JavaChunkDataTranslator extends PacketTranslator { @@ -54,7 +60,6 @@ public class JavaChunkDataTranslator extends PacketTranslator { try { ChunkUtils.ChunkData chunkData = ChunkUtils.translateToBedrock(packet.getColumn()); @@ -78,6 +83,14 @@ public class JavaChunkDataTranslator extends PacketTranslator blockEntityEntry : chunkData.getLoadBlockEntitiesLater().object2IntEntrySet()) { + int x = blockEntityEntry.getKey().getInt("x"); + int y = blockEntityEntry.getKey().getInt("y"); + int z = blockEntityEntry.getKey().getInt("z"); + ChunkUtils.updateBlock(session, new BlockState(blockEntityEntry.getIntValue()), new Position(x, y, z)); + } + chunkData.getLoadBlockEntitiesLater().clear(); + } catch (Exception ex) { ex.printStackTrace(); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.java new file mode 100644 index 00000000..b1158ef1 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/world/JavaUpdateTileEntityTranslator.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.java.world; + +import com.github.steveice10.mc.protocol.packet.ingame.server.world.ServerUpdateTileEntityPacket; + +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.block.entity.BlockEntity; +import org.geysermc.connector.network.translators.block.entity.BlockEntityTranslator; +import org.geysermc.connector.utils.BlockEntityUtils; +import org.geysermc.connector.utils.ChunkUtils; + +import java.util.concurrent.TimeUnit; + +@Translator(packet = ServerUpdateTileEntityPacket.class) +public class JavaUpdateTileEntityTranslator extends PacketTranslator { + + // This should be modified if sign text is not showing up + private static final int DELAY = 500; + + @Override + public void translate(ServerUpdateTileEntityPacket packet, GeyserSession session) { + String id = BlockEntityUtils.getBedrockBlockEntityId(packet.getType().name()); + BlockEntityTranslator translator = BlockEntityUtils.getBlockEntityTranslator(id); + // If not null then the BlockState is used in BlockEntityTranslator.translateTag() + if (ChunkUtils.CACHED_BLOCK_ENTITIES.get(packet.getPosition()) != null) { + BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), + ChunkUtils.CACHED_BLOCK_ENTITIES.get(packet.getPosition())), packet.getPosition()); + ChunkUtils.CACHED_BLOCK_ENTITIES.remove(packet.getPosition()); + } else if (translator.getClass().getAnnotation(BlockEntity.class).delay()) { + // Delay so chunks can finish sending + session.getConnector().getGeneralThreadPool().schedule(() -> + BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), null), packet.getPosition()), + DELAY, TimeUnit.MILLISECONDS); + } else { + BlockEntityUtils.updateBlockEntity(session, translator.getBlockEntityTag(id, packet.getNbt(), null), packet.getPosition()); + } + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java new file mode 100644 index 00000000..0dcd13ad --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockEntityUtils.java @@ -0,0 +1,47 @@ +package org.geysermc.connector.utils; + +import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; +import com.nukkitx.math.vector.Vector3i; +import com.nukkitx.protocol.bedrock.packet.BlockEntityDataPacket; + +import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.block.entity.BlockEntityTranslator; + +public class BlockEntityUtils { + + private static final BlockEntityTranslator EMPTY_TRANSLATOR = Translators.getBlockEntityTranslators().get("Empty"); + + public static String getBedrockBlockEntityId(String id) { + // This is the only exception when it comes to block entity ids + if (id.contains("piston_head")) + return "PistonArm"; + + id = id.toLowerCase() + .replace("minecraft:", "") + .replace("_", " "); + String[] words = id.split(" "); + for (int i = 0; i < words.length; i++) { + words[i] = words[i].substring(0, 1).toUpperCase() + words[i].substring(1).toLowerCase(); + } + + id = String.join(" ", words); + return id.replace(" ", ""); + } + + public static BlockEntityTranslator getBlockEntityTranslator(String name) { + BlockEntityTranslator blockEntityTranslator = Translators.getBlockEntityTranslators().get(name); + if (blockEntityTranslator == null) { + return EMPTY_TRANSLATOR; + } + + return blockEntityTranslator; + } + + public static void updateBlockEntity(GeyserSession session, com.nukkitx.nbt.tag.CompoundTag blockEntity, Position position) { + BlockEntityDataPacket blockEntityPacket = new BlockEntityDataPacket(); + blockEntityPacket.setBlockPosition(Vector3i.from(position.getX(), position.getY(), position.getZ())); + blockEntityPacket.setData(blockEntity); + session.getUpstream().sendPacket(blockEntityPacket); + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java new file mode 100644 index 00000000..34287073 --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/BlockUtils.java @@ -0,0 +1,128 @@ +/* + * 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.utils; + +import com.github.steveice10.mc.protocol.data.game.entity.Effect; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; +import org.geysermc.connector.entity.PlayerEntity; +import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ToolItemEntry; + +public class BlockUtils { + + private static boolean correctTool(String blockToolType, String itemToolType) { + return (blockToolType.equals("sword") && itemToolType.equals("sword")) || + (blockToolType.equals("shovel") && itemToolType.equals("shovel")) || + (blockToolType.equals("pickaxe") && itemToolType.equals("pickaxe")) || + (blockToolType.equals("axe") && itemToolType.equals("axe")) || + (blockToolType.equals("shears") && itemToolType.equals("shears")); + } + + private static double toolBreakTimeBonus(String toolType, String toolTier, boolean isWoolBlock) { + if (toolType.equals("shears")) return isWoolBlock ? 5.0 : 15.0; + if (toolType.equals("")) return 1.0; + switch (toolTier) { + case "wooden": + return 2.0; + case "stone": + return 4.0; + case "iron": + return 6.0; + case "diamond": + return 8.0; + case "golden": + return 12.0; + default: + return 1.0; + } + } + + //http://minecraft.gamepedia.com/Breaking + private static double calculateBreakTime(double blockHardness, String toolTier, boolean canHarvestWithHand, boolean correctTool, + String toolType, boolean isWoolBlock, boolean isCobweb, int toolEfficiencyLevel, int hasteLevel, int miningFatigueLevel + /*boolean insideOfWaterWithoutAquaAffinity, boolean outOfWaterButNotOnGround*/) { + double baseTime = ((correctTool || canHarvestWithHand) ? 1.5 : 5.0) * blockHardness; + double speed = 1.0 / baseTime; + + if (correctTool) { + speed *= toolBreakTimeBonus(toolType, toolTier, isWoolBlock); + speed += toolEfficiencyLevel == 0 ? 0 : toolEfficiencyLevel * toolEfficiencyLevel + 1; + } else if (toolType.equals("sword")) { + speed*= (isCobweb ? 15.0 : 1.5); + } + speed *= 1.0 + (0.2 * hasteLevel); + + switch (miningFatigueLevel) { + case 0: + break; + case 1: + speed -= (speed * 0.7); + break; + case 2: + speed -= (speed * 0.91); + break; + case 3: + speed -= (speed * 0.9973); + break; + default: + speed -= (speed * 0.99919); + break; + } + + //if (insideOfWaterWithoutAquaAffinity) speed *= 0.2; + //if (outOfWaterButNotOnGround) speed *= 0.2; + // else if insideWaterAndNotOnGround speed *= 0.2; + return 1.0 / speed; + } + + public static double getBreakTime(double blockHardness, int blockId, ItemEntry item, CompoundTag nbtData, PlayerEntity player) { + boolean isWoolBlock = BlockTranslator.JAVA_RUNTIME_WOOL_IDS.contains(blockId); + boolean isCobweb = blockId == BlockTranslator.JAVA_RUNTIME_COBWEB_ID; + String blockToolType = BlockTranslator.JAVA_RUNTIME_ID_TO_TOOL_TYPE.getOrDefault(blockId, ""); + boolean canHarvestWithHand = BlockTranslator.JAVA_RUNTIME_ID_TO_CAN_HARVEST_WITH_HAND.get(blockId); + String toolType = ""; + String toolTier = ""; + boolean correctTool = false; + if (item instanceof ToolItemEntry) { + ToolItemEntry toolItem = (ToolItemEntry) item; + toolType = toolItem.getToolType(); + toolTier = toolItem.getToolTier(); + correctTool = correctTool(blockToolType, toolType); + } + int toolEfficiencyLevel = ItemUtils.getEnchantmentLevel(nbtData, "minecraft:efficiency"); + int hasteLevel = player.getEffectCache().getEffectLevel(Effect.FASTER_DIG); + int miningFatigueLevel = player.getEffectCache().getEffectLevel(Effect.SLOWER_DIG); + + // TODO implement these checks and material check if possible + //boolean insideOfWaterWithoutAquaAffinity = player.isInsideOfWater() && + // Optional.ofNullable(player.getInventory().getHelmet().getEnchantment(Enchantment.ID_WATER_WORKER)) + // .map(Enchantment::getLevel).map(l -> l >= 1).orElse(false); + //boolean outOfWaterButNotOnGround = (!player.isInsideOfWater()) && (!player.isOnGround()); + return calculateBreakTime(blockHardness, toolTier, canHarvestWithHand, correctTool, toolType, isWoolBlock, isCobweb, toolEfficiencyLevel, hasteLevel, miningFatigueLevel); + } + +} 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 a35b2cc5..d496215a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ChunkUtils.java @@ -29,27 +29,47 @@ import com.github.steveice10.mc.protocol.data.game.chunk.Chunk; import com.github.steveice10.mc.protocol.data.game.chunk.Column; import com.github.steveice10.mc.protocol.data.game.entity.metadata.Position; import com.github.steveice10.mc.protocol.data.game.world.block.BlockState; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.math.vector.Vector2i; import com.nukkitx.math.vector.Vector3i; import com.nukkitx.protocol.bedrock.packet.LevelChunkPacket; import com.nukkitx.protocol.bedrock.packet.NetworkChunkPublisherUpdatePacket; import com.nukkitx.protocol.bedrock.packet.UpdateBlockPacket; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; + +import lombok.Getter; +import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.session.GeyserSession; +import org.geysermc.connector.network.translators.block.entity.*; import org.geysermc.connector.network.translators.Translators; import org.geysermc.connector.network.translators.block.BlockTranslator; +import org.geysermc.connector.world.chunk.ChunkPosition; import org.geysermc.connector.world.chunk.ChunkSection; +import java.util.HashMap; +import java.util.Map; + import static org.geysermc.connector.network.translators.block.BlockTranslator.BEDROCK_WATER_ID; public class ChunkUtils { + + /** + * Temporarily stores positions of BlockState values that are needed for certain block entities actively + */ + public static final Map CACHED_BLOCK_ENTITIES = new HashMap<>(); + public static ChunkData translateToBedrock(Column column) { ChunkData chunkData = new ChunkData(); - Chunk[] chunks = column.getChunks(); - int chunkSectionCount = chunks.length; - chunkData.sections = new ChunkSection[chunkSectionCount]; + chunkData.sections = new ChunkSection[chunks.length]; - for (int chunkY = 0; chunkY < chunkSectionCount; chunkY++) { + CompoundTag[] blockEntities = column.getTileEntities(); + // Temporarily stores positions of BlockState values per chunk load + Map blockEntityPositions = new HashMap<>(); + + for (int chunkY = 0; chunkY < chunks.length; chunkY++) { chunkData.sections[chunkY] = new ChunkSection(); Chunk chunk = chunks[chunkY]; @@ -57,14 +77,28 @@ public class ChunkUtils { continue; ChunkSection section = chunkData.sections[chunkY]; - for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { BlockState blockState = chunk.get(x, y, z); int id = BlockTranslator.getBedrockBlockId(blockState); - section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); + // Check to see if the name is in BlockTranslator.getBlockEntityString, and therefore must be handled differently + if (BlockTranslator.getBlockEntityString(blockState) != null) { + // Get the block entity translator + BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(BlockTranslator.getBlockEntityString(blockState)); + Position pos = new ChunkPosition(column.getX(), column.getZ()).getBlock(x, (chunkY << 4) + y, z); + blockEntityPositions.put(pos, blockState); + // If there is a delay required for the block, allow it. + if (blockEntityTranslator.getClass().getAnnotation(BlockEntity.class).delay()) { + chunkData.loadBlockEntitiesLater.put(blockEntityTranslator.getDefaultBedrockTag(BlockEntityUtils.getBedrockBlockEntityId(BlockTranslator.getBlockEntityString(blockState)), + pos.getX(), pos.getY(), pos.getZ()), blockState.getId()); + } else { + section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); + } + } else { + section.getBlockStorageArray()[0].setFullBlock(ChunkSection.blockPosition(x, y, z), id); + } if (BlockTranslator.isWaterlogged(blockState)) { section.getBlockStorageArray()[1].setFullBlock(ChunkSection.blockPosition(x, y, z), BEDROCK_WATER_ID); @@ -72,7 +106,27 @@ public class ChunkUtils { } } } + } + + com.nukkitx.nbt.tag.CompoundTag[] bedrockBlockEntities = new com.nukkitx.nbt.tag.CompoundTag[blockEntities.length]; + for (int i = 0; i < blockEntities.length; i++) { + CompoundTag tag = blockEntities[i]; + String tagName; + if (!tag.contains("id")) { + GeyserConnector.getInstance().getLogger().debug("Got tag with no id: " + tag.getValue()); + tagName = "Empty"; + } else { + tagName = (String) tag.get("id").getValue(); + } + + String id = BlockEntityUtils.getBedrockBlockEntityId(tagName); + BlockEntityTranslator blockEntityTranslator = BlockEntityUtils.getBlockEntityTranslator(id); + BlockState blockState = blockEntityPositions.get(new Position((int) tag.get("x").getValue(), (int) tag.get("y").getValue(), (int) tag.get("z").getValue())); + bedrockBlockEntities[i] = blockEntityTranslator.getBlockEntityTag(tagName, tag, blockState); + } + + chunkData.blockEntities = bedrockBlockEntities; return chunkData; } @@ -114,6 +168,19 @@ public class ChunkUtils { waterPacket.setRuntimeId(0); } session.getUpstream().sendPacket(waterPacket); + + // 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 + for (Map.Entry entry : Translators.getBlockEntityTranslators().entrySet()) { + if (entry.getValue() instanceof RequiresBlockState) { + RequiresBlockState requiresBlockState = (RequiresBlockState) entry.getValue(); + if (requiresBlockState.isBlock(blockState)) { + CACHED_BLOCK_ENTITIES.put(new Position(position.getX(), position.getY(), position.getZ()), blockState); + break; //No block will be a part of two classes + } + } + } } public static void sendEmptyChunks(GeyserSession session, Vector3i position, int radius, boolean forceUpdate) { @@ -144,6 +211,9 @@ public class ChunkUtils { public static final class ChunkData { public ChunkSection[] sections; - public byte[] blockEntities = new byte[0]; + @Getter + private com.nukkitx.nbt.tag.CompoundTag[] blockEntities = new com.nukkitx.nbt.tag.CompoundTag[0]; + @Getter + private Object2IntMap loadBlockEntitiesLater = new Object2IntOpenHashMap<>(); } } 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 045b81d1..69886851 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/InventoryUtils.java @@ -26,113 +26,76 @@ package org.geysermc.connector.utils; import com.github.steveice10.mc.protocol.data.game.entity.metadata.ItemStack; -import com.github.steveice10.mc.protocol.packet.ingame.client.window.ClientCloseWindowPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerOpenWindowPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerSetSlotPacket; -import com.github.steveice10.mc.protocol.packet.ingame.server.window.ServerWindowItemsPacket; -import com.github.steveice10.packetlib.packet.Packet; +import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.nukkitx.protocol.bedrock.data.ContainerId; import com.nukkitx.protocol.bedrock.data.ItemData; -import com.nukkitx.protocol.bedrock.packet.InventoryContentPacket; +import com.nukkitx.protocol.bedrock.packet.InventorySlotPacket; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.inventory.Inventory; import org.geysermc.connector.network.session.GeyserSession; import org.geysermc.connector.network.translators.Translators; +import org.geysermc.connector.network.translators.inventory.DoubleChestInventoryTranslator; import org.geysermc.connector.network.translators.inventory.InventoryTranslator; -import java.util.List; +import java.util.Objects; import java.util.concurrent.TimeUnit; public class InventoryUtils { + public static final ItemStack REFRESH_ITEM = new ItemStack(1, 127, new CompoundTag("")); //TODO: stop using this - public static void refreshPlayerInventory(GeyserSession session, Inventory inventory) { - InventoryContentPacket inventoryContentPacket = new InventoryContentPacket(); - inventoryContentPacket.setContainerId(ContainerId.INVENTORY); - - ItemData[] contents = new ItemData[40]; - // Inventory - for (int i = 9; i < 36; i++) { - contents[i] = Translators.getItemTranslator().translateToBedrock(inventory.getItems()[i]); + public static void openInventory(GeyserSession session, Inventory inventory) { + InventoryTranslator translator = Translators.getInventoryTranslators().get(inventory.getWindowType()); + if (translator != null) { + session.getInventoryCache().setOpenInventory(inventory); + translator.prepareInventory(session, inventory); + //TODO: find better way to handle double chest delay + if (translator instanceof DoubleChestInventoryTranslator) { + GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { + translator.openInventory(session, inventory); + translator.updateInventory(session, inventory); + }, 200, TimeUnit.MILLISECONDS); + } else { + translator.openInventory(session, inventory); + translator.updateInventory(session, inventory); + } } - - // Hotbar - for (int i = 36; i < 45; i++) { - contents[i - 36] = Translators.getItemTranslator().translateToBedrock(inventory.getItems()[i]); - } - - // Armor - for (int i = 5; i < 9; i++) { - contents[i + 31] = Translators.getItemTranslator().translateToBedrock(inventory.getItems()[i]); - } - - inventoryContentPacket.setContents(contents); - session.getUpstream().sendPacket(inventoryContentPacket); } - public static void openInventory(GeyserSession session, ServerOpenWindowPacket packet) { - Inventory inventory = new Inventory(packet.getWindowId(), packet.getType(), 45); // TODO: Find a way to set this value - session.getInventoryCache().getInventories().put(packet.getWindowId(), inventory); - session.getInventoryCache().setOpenInventory(inventory); - - InventoryTranslator translator = Translators.getInventoryTranslator(); - translator.prepareInventory(session, inventory); - GeyserConnector.getInstance().getGeneralThreadPool().schedule(() -> { - List packets = session.getInventoryCache().getCachedPackets().get(inventory.getId()); - packets.forEach(itemPacket -> { - if (itemPacket != null) { - if (ServerWindowItemsPacket.class.isAssignableFrom(itemPacket.getClass())) { - updateInventory(session, (ServerWindowItemsPacket) itemPacket); - } - } - }); - }, 200, TimeUnit.MILLISECONDS); + public static void closeInventory(GeyserSession session, int windowId) { + if (windowId != 0) { + Inventory inventory = session.getInventoryCache().getInventories().get(windowId); + if (inventory != null) { + InventoryTranslator translator = Translators.getInventoryTranslators().get(inventory.getWindowType()); + translator.closeInventory(session, inventory); + session.getInventoryCache().uncacheInventory(windowId); + session.getInventoryCache().setOpenInventory(null); + } + } else { + Inventory inventory = session.getInventory(); + InventoryTranslator translator = Translators.getInventoryTranslators().get(inventory.getWindowType()); + translator.updateInventory(session, inventory); + } + session.setCraftSlot(0); + session.getInventory().setCursor(null); } - public static void updateInventory(GeyserSession session, ServerWindowItemsPacket packet) { - if (packet.getWindowId() == 0) - return; - - if (session.getInventoryCache().getOpenInventory() == null || !session.getInventoryCache().getInventories().containsKey(packet.getWindowId())) - return; - - Inventory openInventory = session.getInventoryCache().getOpenInventory(); - if (packet.getWindowId() != openInventory.getId()) - return; - - InventoryTranslator translator = Translators.getInventoryTranslator(); - if (translator == null) { - session.getDownstream().getSession().send(new ClientCloseWindowPacket(packet.getWindowId())); - return; - } - - openInventory.setItems(packet.getItems()); - translator.updateInventory(session, openInventory); + public static void updateCursor(GeyserSession session) { + InventorySlotPacket cursorPacket = new InventorySlotPacket(); + cursorPacket.setContainerId(ContainerId.CURSOR); + cursorPacket.setSlot(0); + cursorPacket.setItem(Translators.getItemTranslator().translateToBedrock(session.getInventory().getCursor())); + session.getUpstream().sendPacket(cursorPacket); } - public static void updateSlot(GeyserSession session, ServerSetSlotPacket packet) { - if (packet.getWindowId() == 0) - return; + public static boolean canStack(ItemStack item1, ItemStack item2) { + if (item1 == null || item2 == null) + return false; + return item1.getId() == item2.getId() && Objects.equals(item1.getNbt(), item2.getNbt()); + } - if (session.getInventoryCache().getOpenInventory() == null || !session.getInventoryCache().getInventories().containsKey(packet.getWindowId())) - return; - - Inventory openInventory = session.getInventoryCache().getOpenInventory(); - if (packet.getWindowId() != openInventory.getId()) - return; - - InventoryTranslator translator = Translators.getInventoryTranslator(); - if (translator == null) { - session.getDownstream().getSession().send(new ClientCloseWindowPacket(packet.getWindowId())); - return; - } - - if (packet.getSlot() >= openInventory.getSize()) { - session.getDownstream().getSession().send(new ClientCloseWindowPacket(packet.getWindowId())); - return; - } - - ItemStack[] items = openInventory.getItems(); - items[packet.getSlot()] = packet.getItem(); - translator.updateSlot(session, openInventory, packet.getSlot()); + public static boolean canStack(ItemData item1, ItemData item2) { + if (item1 == null || item2 == null) + return false; + return item1.equals(item2, false, true, true); } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java b/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.java new file mode 100644 index 00000000..bb3cf0ed --- /dev/null +++ b/connector/src/main/java/org/geysermc/connector/utils/ItemUtils.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.utils; + +import com.github.steveice10.opennbt.tag.builtin.*; + +public class ItemUtils { + + public static int getEnchantmentLevel(CompoundTag itemNBTData, String enchantmentId) { + ListTag enchantments = (itemNBTData == null ? null : itemNBTData.get("Enchantments")); + if (enchantments != null) { + int enchantmentLevel = 0; + for (Tag tag : enchantments) { + CompoundTag enchantment = (CompoundTag) tag; + StringTag enchantId = enchantment.get("id"); + if (enchantId.getValue().equals(enchantmentId)) { + enchantmentLevel = (int) ((ShortTag) enchantment.get("lvl")).getValue(); + } + } + return enchantmentLevel; + } + return 0; + } +} diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java index 9480cffd..012bd73a 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -191,7 +191,6 @@ public class SkinUtils { if (skinAndCapeConsumer != null) skinAndCapeConsumer.accept(skinAndCape); }); - }); } diff --git a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java index 989f9a3a..a2978e9d 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java +++ b/connector/src/main/java/org/geysermc/connector/utils/Toolbox.java @@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.nukkitx.nbt.NbtUtils; import com.nukkitx.nbt.stream.NBTInputStream; import com.nukkitx.nbt.tag.CompoundTag; +import com.nukkitx.protocol.bedrock.data.ItemData; import com.nukkitx.protocol.bedrock.packet.StartGamePacket; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -39,6 +40,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.network.translators.item.ItemEntry; +import org.geysermc.connector.network.translators.item.ToolItemEntry; import java.io.*; import java.util.*; @@ -47,6 +49,7 @@ public class Toolbox { public static final ObjectMapper JSON_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES); public static final CompoundTag BIOMES; + public static final ItemData[] CREATIVE_ITEMS; public static final List ITEMS = new ArrayList<>(); @@ -101,13 +104,63 @@ public class Toolbox { Iterator> iterator = items.fields(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); - ITEM_ENTRIES.put(itemIndex, new ItemEntry(entry.getKey(), itemIndex, - entry.getValue().get("bedrock_id").intValue(), entry.getValue().get("bedrock_data").intValue())); + if (entry.getValue().has("tool_type")) { + if (entry.getValue().has("tool_tier")) { + ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( + entry.getKey(), itemIndex, + entry.getValue().get("bedrock_id").intValue(), + entry.getValue().get("bedrock_data").intValue(), + entry.getValue().get("tool_type").textValue(), + entry.getValue().get("tool_tier").textValue())); + } else { + ITEM_ENTRIES.put(itemIndex, new ToolItemEntry( + entry.getKey(), itemIndex, + entry.getValue().get("bedrock_id").intValue(), + entry.getValue().get("bedrock_data").intValue(), + entry.getValue().get("tool_type").textValue(), + "")); + } + } else { + ITEM_ENTRIES.put(itemIndex, new ItemEntry( + entry.getKey(), itemIndex, + entry.getValue().get("bedrock_id").intValue(), + entry.getValue().get("bedrock_data").intValue())); + } itemIndex++; } // Load the locale data LocaleUtils.init(); + + stream = getResource("bedrock/creative_items.json"); + + JsonNode creativeItemEntries; + try { + creativeItemEntries = JSON_MAPPER.readTree(stream).get("items"); + } catch (Exception e) { + throw new AssertionError("Unable to load creative items", e); + } + + List creativeItems = new ArrayList<>(); + for (JsonNode itemNode : creativeItemEntries) { + short damage = 0; + if (itemNode.has("damage")) { + damage = itemNode.get("damage").numberValue().shortValue(); + } + if (itemNode.has("nbt_b64")) { + byte[] bytes = Base64.getDecoder().decode(itemNode.get("nbt_b64").asText()); + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + try { + com.nukkitx.nbt.tag.CompoundTag tag = (com.nukkitx.nbt.tag.CompoundTag) NbtUtils.createReaderLE(bais).readTag(); + creativeItems.add(ItemData.of(itemNode.get("id").asInt(), damage, 1, tag)); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + creativeItems.add(ItemData.of(itemNode.get("id").asInt(), damage, 1)); + } + } + CREATIVE_ITEMS = creativeItems.toArray(new ItemData[0]); } /** diff --git a/connector/src/main/java/org/geysermc/connector/world/chunk/ChunkPosition.java b/connector/src/main/java/org/geysermc/connector/world/chunk/ChunkPosition.java index cafb7ab0..c45a7c94 100644 --- a/connector/src/main/java/org/geysermc/connector/world/chunk/ChunkPosition.java +++ b/connector/src/main/java/org/geysermc/connector/world/chunk/ChunkPosition.java @@ -48,7 +48,6 @@ public class ChunkPosition { int chunkX = x & 15; int chunkY = y & 15; int chunkZ = z & 15; - return new Position(chunkX, chunkY, chunkZ); } } diff --git a/connector/src/main/resources/bedrock/creative_items.json b/connector/src/main/resources/bedrock/creative_items.json index d55839ba..8045b219 100644 --- a/connector/src/main/resources/bedrock/creative_items.json +++ b/connector/src/main/resources/bedrock/creative_items.json @@ -3008,21 +3008,6 @@ { "id" : -198 }, - { - "id" : 238, - "damage" : 8 - }, - { - "id" : 238 - }, - { - "id" : 238, - "damage" : 12 - }, - { - "id" : 238, - "damage" : 4 - }, { "id" : 379 }, @@ -3419,363 +3404,6 @@ { "id" : 386 }, - { - "id" : 36 - }, - { - "id" : -12 - }, - { - "id" : -13 - }, - { - "id" : -14 - }, - { - "id" : -15 - }, - { - "id" : -16 - }, - { - "id" : -17 - }, - { - "id" : -18 - }, - { - "id" : -19 - }, - { - "id" : -20 - }, - { - "id" : -21 - }, - { - "id" : -22 - }, - { - "id" : -23 - }, - { - "id" : -24 - }, - { - "id" : -25 - }, - { - "id" : -26 - }, - { - "id" : -27 - }, - { - "id" : -28 - }, - { - "id" : -29 - }, - { - "id" : -30 - }, - { - "id" : -31 - }, - { - "id" : -32 - }, - { - "id" : -33 - }, - { - "id" : -34 - }, - { - "id" : -35 - }, - { - "id" : -36 - }, - { - "id" : -37 - }, - { - "id" : -38 - }, - { - "id" : -39 - }, - { - "id" : -40 - }, - { - "id" : -41 - }, - { - "id" : -42 - }, - { - "id" : -43 - }, - { - "id" : -44 - }, - { - "id" : -45 - }, - { - "id" : -46 - }, - { - "id" : -47 - }, - { - "id" : -48 - }, - { - "id" : -49 - }, - { - "id" : -50 - }, - { - "id" : -51 - }, - { - "id" : -52 - }, - { - "id" : -53 - }, - { - "id" : -54 - }, - { - "id" : -55 - }, - { - "id" : -56 - }, - { - "id" : -57 - }, - { - "id" : -58 - }, - { - "id" : -59 - }, - { - "id" : -60 - }, - { - "id" : -61 - }, - { - "id" : -62 - }, - { - "id" : -63 - }, - { - "id" : -64 - }, - { - "id" : -65 - }, - { - "id" : -66 - }, - { - "id" : -67 - }, - { - "id" : -68 - }, - { - "id" : -69 - }, - { - "id" : -70 - }, - { - "id" : -71 - }, - { - "id" : -72 - }, - { - "id" : -73 - }, - { - "id" : -74 - }, - { - "id" : -75 - }, - { - "id" : -76 - }, - { - "id" : -77 - }, - { - "id" : -78 - }, - { - "id" : -79 - }, - { - "id" : -80 - }, - { - "id" : -81 - }, - { - "id" : -82 - }, - { - "id" : -83 - }, - { - "id" : -84 - }, - { - "id" : -85 - }, - { - "id" : -86 - }, - { - "id" : -87 - }, - { - "id" : -88 - }, - { - "id" : -89 - }, - { - "id" : -90 - }, - { - "id" : -91 - }, - { - "id" : -92 - }, - { - "id" : -93 - }, - { - "id" : -94 - }, - { - "id" : -95 - }, - { - "id" : -96 - }, - { - "id" : -97 - }, - { - "id" : -98 - }, - { - "id" : -99 - }, - { - "id" : -100 - }, - { - "id" : -101 - }, - { - "id" : -102 - }, - { - "id" : -103 - }, - { - "id" : -104 - }, - { - "id" : -105 - }, - { - "id" : -106 - }, - { - "id" : -107 - }, - { - "id" : -108 - }, - { - "id" : -109 - }, - { - "id" : -110 - }, - { - "id" : -111 - }, - { - "id" : -112 - }, - { - "id" : -113 - }, - { - "id" : -114 - }, - { - "id" : -115 - }, - { - "id" : -116 - }, - { - "id" : -117 - }, - { - "id" : -118 - }, - { - "id" : -119 - }, - { - "id" : -120 - }, - { - "id" : -121 - }, - { - "id" : -122 - }, - { - "id" : -123 - }, - { - "id" : -124 - }, - { - "id" : -125 - }, - { - "id" : -126 - }, - { - "id" : -127 - }, - { - "id" : -128 - }, - { - "id" : -129 - }, { "id" : 403, "nbt_b64" : "CgAACQQAZW5jaAoBAAAAAgMAbHZsAQACAgBpZAAAAAA=" diff --git a/connector/src/main/resources/mappings b/connector/src/main/resources/mappings index efc9db6b..a6a790d9 160000 --- a/connector/src/main/resources/mappings +++ b/connector/src/main/resources/mappings @@ -1 +1 @@ -Subproject commit efc9db6b7d51bdf145230933ac23b321ac1c132d +Subproject commit a6a790d944eb153c93d0c414813d8a0880fd5d26