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);