From 3cd01978dfdb4e6a2e92a6bba26ae3050aa8eb84 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Fri, 6 Dec 2024 12:55:01 +0100 Subject: [PATCH] Implement block and entity picking --- .../api/minecraft/BlockPosition.java | 7 + .../bukkit/platform/BukkitViaLoader.java | 12 +- .../bukkit/platform/PaperViaInjector.java | 32 +-- .../providers/BukkitPickItemProvider.java | 182 ++++++++++++++++++ .../Protocol1_21_2To1_21_4.java | 7 + .../provider/PickItemProvider.java | 31 +++ .../BlockItemPacketRewriter1_21_4.java | 17 +- 7 files changed, 271 insertions(+), 17 deletions(-) create mode 100644 bukkit/src/main/java/com/viaversion/viaversion/bukkit/providers/BukkitPickItemProvider.java create mode 100644 common/src/main/java/com/viaversion/viaversion/protocols/v1_21_2to1_21_4/provider/PickItemProvider.java diff --git a/api/src/main/java/com/viaversion/viaversion/api/minecraft/BlockPosition.java b/api/src/main/java/com/viaversion/viaversion/api/minecraft/BlockPosition.java index f8ab744d0..e723379cf 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/minecraft/BlockPosition.java +++ b/api/src/main/java/com/viaversion/viaversion/api/minecraft/BlockPosition.java @@ -37,6 +37,13 @@ public class BlockPosition { return new BlockPosition(x + face.modX(), y + face.modY(), z + face.modZ()); } + public double distanceFromCenterSquared(final double x, final double y, final double z) { + final double dx = this.x + 0.5 - x; + final double dy = this.y + 0.5 - y; + final double dz = this.z + 0.5 - z; + return dx * dx + dy * dy + dz * dz; + } + public int x() { return x; } diff --git a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/BukkitViaLoader.java b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/BukkitViaLoader.java index 83d33f6e0..e3d82be67 100644 --- a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/BukkitViaLoader.java +++ b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/BukkitViaLoader.java @@ -26,24 +26,25 @@ import com.viaversion.viaversion.api.protocol.version.ProtocolVersion; import com.viaversion.viaversion.bukkit.listeners.UpdateListener; import com.viaversion.viaversion.bukkit.listeners.multiversion.PlayerSneakListener; import com.viaversion.viaversion.bukkit.listeners.v1_14_4to1_15.EntityToggleGlideListener; -import com.viaversion.viaversion.bukkit.listeners.v1_19_3to1_19_4.ArmorToggleListener; import com.viaversion.viaversion.bukkit.listeners.v1_18_2to1_19.BlockBreakListener; +import com.viaversion.viaversion.bukkit.listeners.v1_19_3to1_19_4.ArmorToggleListener; import com.viaversion.viaversion.bukkit.listeners.v1_20_5to1_21.LegacyChangeItemListener; +import com.viaversion.viaversion.bukkit.listeners.v1_20_5to1_21.PaperPlayerChangeItemListener; import com.viaversion.viaversion.bukkit.listeners.v1_8to1_9.ArmorListener; import com.viaversion.viaversion.bukkit.listeners.v1_8to1_9.BlockListener; import com.viaversion.viaversion.bukkit.listeners.v1_8to1_9.DeathListener; import com.viaversion.viaversion.bukkit.listeners.v1_8to1_9.HandItemCache; import com.viaversion.viaversion.bukkit.listeners.v1_8to1_9.PaperPatch; -import com.viaversion.viaversion.bukkit.listeners.v1_20_5to1_21.PaperPlayerChangeItemListener; -import com.viaversion.viaversion.bukkit.listeners.v1_20_5to1_21.PlayerChangeItemListener; import com.viaversion.viaversion.bukkit.providers.BukkitAckSequenceProvider; import com.viaversion.viaversion.bukkit.providers.BukkitBlockConnectionProvider; import com.viaversion.viaversion.bukkit.providers.BukkitInventoryQuickMoveProvider; +import com.viaversion.viaversion.bukkit.providers.BukkitPickItemProvider; import com.viaversion.viaversion.bukkit.providers.BukkitViaMovementTransmitter; import com.viaversion.viaversion.protocols.v1_11_1to1_12.provider.InventoryQuickMoveProvider; import com.viaversion.viaversion.protocols.v1_12_2to1_13.blockconnections.ConnectionData; import com.viaversion.viaversion.protocols.v1_12_2to1_13.blockconnections.providers.BlockConnectionProvider; import com.viaversion.viaversion.protocols.v1_18_2to1_19.provider.AckSequenceProvider; +import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.provider.PickItemProvider; import com.viaversion.viaversion.protocols.v1_8to1_9.provider.HandItemProvider; import com.viaversion.viaversion.protocols.v1_8to1_9.provider.MovementTransmitterProvider; import java.util.HashSet; @@ -188,6 +189,11 @@ public class BukkitViaLoader implements ViaPlatformLoader { new LegacyChangeItemListener(plugin).register(); } } + if (serverProtocolVersion.olderThan(ProtocolVersion.v1_21_4)) { + if (PaperViaInjector.hasMethod(Material.class, "isItem")) { + Via.getManager().getProviders().use(PickItemProvider.class, new BukkitPickItemProvider(plugin)); + } + } } private boolean hasGetHandMethod() { diff --git a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/PaperViaInjector.java b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/PaperViaInjector.java index 366ad3ecb..78b370c13 100644 --- a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/PaperViaInjector.java +++ b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/PaperViaInjector.java @@ -65,12 +65,7 @@ public final class PaperViaInjector { } private static boolean hasServerProtocolMethod() { - try { - Class.forName("org.bukkit.UnsafeValues").getDeclaredMethod("getProtocolVersion"); - return true; - } catch (final ClassNotFoundException | NoSuchMethodException e) { - return false; - } + return hasMethod("org.bukkit.UnsafeValues", "getProtocolVersion"); } private static boolean hasPaperInjectionMethod() { @@ -78,12 +73,7 @@ public final class PaperViaInjector { } private static boolean hasIsStoppingMethod() { - try { - Bukkit.class.getDeclaredMethod("isStopping"); - return true; - } catch (final NoSuchMethodException e) { - return false; - } + return hasMethod(Bukkit.class, "isStopping"); } private static boolean hasPacketLimiter() { @@ -98,4 +88,22 @@ public final class PaperViaInjector { return false; } } + + public static boolean hasMethod(final String className, final String method) { + try { + Class.forName(className).getDeclaredMethod(method); + return true; + } catch (final ClassNotFoundException | NoSuchMethodException e) { + return false; + } + } + + public static boolean hasMethod(final Class clazz, final String method) { + try { + clazz.getDeclaredMethod(method); + return true; + } catch (final NoSuchMethodException e) { + return false; + } + } } diff --git a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/providers/BukkitPickItemProvider.java b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/providers/BukkitPickItemProvider.java new file mode 100644 index 000000000..7cedde50f --- /dev/null +++ b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/providers/BukkitPickItemProvider.java @@ -0,0 +1,182 @@ +/* + * This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion + * Copyright (C) 2016-2024 ViaVersion and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.viaversion.viaversion.bukkit.providers; + +import com.viaversion.viaversion.ViaVersionPlugin; +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.BlockPosition; +import com.viaversion.viaversion.bukkit.platform.PaperViaInjector; +import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.provider.PickItemProvider; +import java.util.UUID; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.checkerframework.checker.nullness.qual.Nullable; + +public final class BukkitPickItemProvider extends PickItemProvider { + private static final boolean HAS_PLACEMENT_MATERIAL_METHOD = PaperViaInjector.hasMethod("org.bukkit.block.BlockData", "getPlacementMaterial"); + private static final boolean HAS_SPAWN_EGG_METHOD = PaperViaInjector.hasMethod(ItemFactory.class, "getSpawnEgg"); + private static final double BLOCK_RANGE = 4.5 + 1; + private static final double BLOCK_RANGE_SQUARED = BLOCK_RANGE * BLOCK_RANGE; + private static final double ENTITY_RANGE = 3 + 3; + private final ViaVersionPlugin plugin; + + public BukkitPickItemProvider(final ViaVersionPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void pickItemFromBlock(final UserConnection connection, final BlockPosition blockPosition, final boolean includeData) { + final UUID uuid = connection.getProtocolInfo().getUuid(); + plugin.getServer().getScheduler().runTask(plugin, () -> { + final Player player = plugin.getServer().getPlayer(uuid); + if (player == null) { + return; + } + + final Location playerLocation = player.getLocation(); + if (blockPosition.distanceFromCenterSquared(playerLocation.getX(), playerLocation.getY(), playerLocation.getZ()) > BLOCK_RANGE_SQUARED) { + return; + } + + final Block block = player.getWorld().getBlockAt(blockPosition.x(), blockPosition.y(), blockPosition.z()); + if (block.getType() == Material.AIR) { + return; + } + + final ItemStack item = blockToItem(block, includeData && player.getGameMode() == GameMode.CREATIVE); + if (item != null) { + pickItem(player, item); + } + }); + } + + private @Nullable ItemStack blockToItem(final Block block, final boolean includeData) { + if (HAS_PLACEMENT_MATERIAL_METHOD) { + final ItemStack item = new ItemStack(block.getBlockData().getPlacementMaterial(), 1); + if (includeData && item.getItemMeta() instanceof final BlockStateMeta blockStateMeta) { + blockStateMeta.setBlockState(block.getState()); + } + return item; + } else if (block.getType().isItem()) { + return new ItemStack(block.getType(), 1); + } + return null; + } + + @Override + public void pickItemFromEntity(final UserConnection connection, final int entityId, final boolean includeData) { + if (!HAS_SPAWN_EGG_METHOD) { + return; + } + + final UUID uuid = connection.getProtocolInfo().getUuid(); + plugin.getServer().getScheduler().runTask(plugin, () -> { + final Player player = plugin.getServer().getPlayer(uuid); + if (player == null) { + return; + } + + final Entity entity = player.getWorld().getNearbyEntities(player.getLocation(), ENTITY_RANGE, ENTITY_RANGE, ENTITY_RANGE).stream() + .filter(e -> e.getEntityId() == entityId) + .findAny() + .orElse(null); + if (entity == null) { + return; + } + + final Material spawnEggType = Bukkit.getItemFactory().getSpawnEgg(entity.getType()); + if (spawnEggType != null) { + pickItem(player, new ItemStack(spawnEggType, 1)); + } + }); + } + + private void pickItem(final Player player, final ItemStack item) { + // Find matching item + final PlayerInventory inventory = player.getInventory(); + final ItemStack[] contents = inventory.getStorageContents(); + int sourceSlot = -1; + for (int i = 0; i < contents.length; i++) { + final ItemStack content = contents[i]; + if (content == null || !content.isSimilar(item)) { + continue; + } + + sourceSlot = i; + break; + } + + if (sourceSlot != -1) { + moveToHotbar(inventory, sourceSlot, contents); + } else if (player.getGameMode() == GameMode.CREATIVE) { + spawnItem(item, inventory, contents); + } + } + + private void spawnItem(final ItemStack item, final PlayerInventory inventory, final ItemStack[] contents) { + final int targetSlot = findEmptyHotbarSlot(inventory, inventory.getHeldItemSlot()); + inventory.setHeldItemSlot(targetSlot); + final ItemStack heldItem = inventory.getItem(targetSlot); + int emptySlot = targetSlot; + if (heldItem != null && heldItem.getType() != Material.AIR) { + // Swap to the first free slot in the inventory, else add it to the current hotbar slot + for (int i = 0; i < contents.length; i++) { + if (contents[i] == null || contents[i].getType() == Material.AIR) { + emptySlot = i; + break; + } + } + } + + inventory.setItem(emptySlot, heldItem); + inventory.setItemInMainHand(item); + } + + private void moveToHotbar(final PlayerInventory inventory, final int sourceSlot, final ItemStack[] contents) { + if (sourceSlot <= 9) { + inventory.setHeldItemSlot(sourceSlot); + return; + } + + final int heldSlot = inventory.getHeldItemSlot(); + final int targetSlot = findEmptyHotbarSlot(inventory, heldSlot); + inventory.setHeldItemSlot(targetSlot); + final ItemStack heldItem = inventory.getItem(targetSlot); + inventory.setItemInMainHand(contents[sourceSlot]); + inventory.setItem(sourceSlot, heldItem); + } + + private int findEmptyHotbarSlot(final PlayerInventory inventory, final int heldSlot) { + for (int i = 0; i < 9; i++) { + final ItemStack item = inventory.getItem(i); + if (item == null || item.getType() == Material.AIR) { + return i; + } + } + return heldSlot; + } +} diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/v1_21_2to1_21_4/Protocol1_21_2To1_21_4.java b/common/src/main/java/com/viaversion/viaversion/protocols/v1_21_2to1_21_4/Protocol1_21_2To1_21_4.java index acbff6695..4b1a581a4 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/v1_21_2to1_21_4/Protocol1_21_2To1_21_4.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/v1_21_2to1_21_4/Protocol1_21_2To1_21_4.java @@ -23,6 +23,7 @@ import com.viaversion.viaversion.api.data.MappingDataBase; import com.viaversion.viaversion.api.minecraft.Particle; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_4; +import com.viaversion.viaversion.api.platform.providers.ViaProviders; import com.viaversion.viaversion.api.protocol.AbstractProtocol; import com.viaversion.viaversion.api.protocol.packet.provider.PacketTypesProvider; import com.viaversion.viaversion.api.protocol.packet.provider.SimplePacketTypesProvider; @@ -35,6 +36,7 @@ import com.viaversion.viaversion.protocols.v1_20_3to1_20_5.packet.ServerboundCon import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundConfigurationPackets1_21; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPacket1_21_4; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPackets1_21_4; +import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.provider.PickItemProvider; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.rewriter.BlockItemPacketRewriter1_21_4; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.rewriter.ComponentRewriter1_21_4; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.rewriter.EntityPacketRewriter1_21_4; @@ -190,6 +192,11 @@ public final class Protocol1_21_2To1_21_4 extends AbstractProtocol. + */ +package com.viaversion.viaversion.protocols.v1_21_2to1_21_4.provider; + +import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.BlockPosition; +import com.viaversion.viaversion.api.platform.providers.Provider; + +public class PickItemProvider implements Provider { + + public void pickItemFromBlock(final UserConnection connection, final BlockPosition blockPosition, final boolean includeData) { + } + + public void pickItemFromEntity(final UserConnection connection, final int entityId, final boolean includeData) { + } +} diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/v1_21_2to1_21_4/rewriter/BlockItemPacketRewriter1_21_4.java b/common/src/main/java/com/viaversion/viaversion/protocols/v1_21_2to1_21_4/rewriter/BlockItemPacketRewriter1_21_4.java index 8e4bd1b24..9a9090d7f 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/v1_21_2to1_21_4/rewriter/BlockItemPacketRewriter1_21_4.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/v1_21_2to1_21_4/rewriter/BlockItemPacketRewriter1_21_4.java @@ -19,7 +19,9 @@ package com.viaversion.viaversion.protocols.v1_21_2to1_21_4.rewriter; import com.viaversion.nbt.tag.CompoundTag; import com.viaversion.nbt.tag.IntTag; +import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; +import com.viaversion.viaversion.api.minecraft.BlockPosition; import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer; import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey; import com.viaversion.viaversion.api.minecraft.item.Item; @@ -31,6 +33,7 @@ import com.viaversion.viaversion.api.type.types.version.Types1_21_4; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.Protocol1_21_2To1_21_4; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPacket1_21_4; import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPackets1_21_4; +import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.provider.PickItemProvider; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2; import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2; import com.viaversion.viaversion.rewriter.BlockRewriter; @@ -61,8 +64,18 @@ public final class BlockItemPacketRewriter1_21_4 extends StructuredItemRewriter< wrapper.write(Types.VAR_INT, (int) slot); }); - protocol.cancelServerbound(ServerboundPackets1_21_4.PICK_ITEM_FROM_BLOCK); - protocol.cancelServerbound(ServerboundPackets1_21_4.PICK_ITEM_FROM_ENTITY); + protocol.registerServerbound(ServerboundPackets1_21_4.PICK_ITEM_FROM_BLOCK, null, wrapper -> { + final BlockPosition blockPosition = wrapper.read(Types.BLOCK_POSITION1_14); + final boolean includeData = wrapper.read(Types.BOOLEAN); + Via.getManager().getProviders().get(PickItemProvider.class).pickItemFromBlock(wrapper.user(), blockPosition, includeData); + wrapper.cancel(); + }); + protocol.registerServerbound(ServerboundPackets1_21_4.PICK_ITEM_FROM_ENTITY, null, wrapper -> { + final int entityId = wrapper.read(Types.VAR_INT); + final boolean includeData = wrapper.read(Types.BOOLEAN); + Via.getManager().getProviders().get(PickItemProvider.class).pickItemFromEntity(wrapper.user(), entityId, includeData); + wrapper.cancel(); + }); protocol.registerClientbound(ClientboundPackets1_21_2.SET_CURSOR_ITEM, this::passthroughClientboundItem); registerSetPlayerInventory(ClientboundPackets1_21_2.SET_PLAYER_INVENTORY);