Skip to content

Commit 3cd0197

Browse files
committed
Implement block and entity picking
1 parent 0c4dbbe commit 3cd0197

File tree

7 files changed

+271
-17
lines changed

7 files changed

+271
-17
lines changed

api/src/main/java/com/viaversion/viaversion/api/minecraft/BlockPosition.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ public BlockPosition getRelative(BlockFace face) {
3737
return new BlockPosition(x + face.modX(), y + face.modY(), z + face.modZ());
3838
}
3939

40+
public double distanceFromCenterSquared(final double x, final double y, final double z) {
41+
final double dx = this.x + 0.5 - x;
42+
final double dy = this.y + 0.5 - y;
43+
final double dz = this.z + 0.5 - z;
44+
return dx * dx + dy * dy + dz * dz;
45+
}
46+
4047
public int x() {
4148
return x;
4249
}

bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/BukkitViaLoader.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,25 @@
2626
import com.viaversion.viaversion.bukkit.listeners.UpdateListener;
2727
import com.viaversion.viaversion.bukkit.listeners.multiversion.PlayerSneakListener;
2828
import com.viaversion.viaversion.bukkit.listeners.v1_14_4to1_15.EntityToggleGlideListener;
29-
import com.viaversion.viaversion.bukkit.listeners.v1_19_3to1_19_4.ArmorToggleListener;
3029
import com.viaversion.viaversion.bukkit.listeners.v1_18_2to1_19.BlockBreakListener;
30+
import com.viaversion.viaversion.bukkit.listeners.v1_19_3to1_19_4.ArmorToggleListener;
3131
import com.viaversion.viaversion.bukkit.listeners.v1_20_5to1_21.LegacyChangeItemListener;
32+
import com.viaversion.viaversion.bukkit.listeners.v1_20_5to1_21.PaperPlayerChangeItemListener;
3233
import com.viaversion.viaversion.bukkit.listeners.v1_8to1_9.ArmorListener;
3334
import com.viaversion.viaversion.bukkit.listeners.v1_8to1_9.BlockListener;
3435
import com.viaversion.viaversion.bukkit.listeners.v1_8to1_9.DeathListener;
3536
import com.viaversion.viaversion.bukkit.listeners.v1_8to1_9.HandItemCache;
3637
import com.viaversion.viaversion.bukkit.listeners.v1_8to1_9.PaperPatch;
37-
import com.viaversion.viaversion.bukkit.listeners.v1_20_5to1_21.PaperPlayerChangeItemListener;
38-
import com.viaversion.viaversion.bukkit.listeners.v1_20_5to1_21.PlayerChangeItemListener;
3938
import com.viaversion.viaversion.bukkit.providers.BukkitAckSequenceProvider;
4039
import com.viaversion.viaversion.bukkit.providers.BukkitBlockConnectionProvider;
4140
import com.viaversion.viaversion.bukkit.providers.BukkitInventoryQuickMoveProvider;
41+
import com.viaversion.viaversion.bukkit.providers.BukkitPickItemProvider;
4242
import com.viaversion.viaversion.bukkit.providers.BukkitViaMovementTransmitter;
4343
import com.viaversion.viaversion.protocols.v1_11_1to1_12.provider.InventoryQuickMoveProvider;
4444
import com.viaversion.viaversion.protocols.v1_12_2to1_13.blockconnections.ConnectionData;
4545
import com.viaversion.viaversion.protocols.v1_12_2to1_13.blockconnections.providers.BlockConnectionProvider;
4646
import com.viaversion.viaversion.protocols.v1_18_2to1_19.provider.AckSequenceProvider;
47+
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.provider.PickItemProvider;
4748
import com.viaversion.viaversion.protocols.v1_8to1_9.provider.HandItemProvider;
4849
import com.viaversion.viaversion.protocols.v1_8to1_9.provider.MovementTransmitterProvider;
4950
import java.util.HashSet;
@@ -188,6 +189,11 @@ public Item getHandItem(final UserConnection info) {
188189
new LegacyChangeItemListener(plugin).register();
189190
}
190191
}
192+
if (serverProtocolVersion.olderThan(ProtocolVersion.v1_21_4)) {
193+
if (PaperViaInjector.hasMethod(Material.class, "isItem")) {
194+
Via.getManager().getProviders().use(PickItemProvider.class, new BukkitPickItemProvider(plugin));
195+
}
196+
}
191197
}
192198

193199
private boolean hasGetHandMethod() {

bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/PaperViaInjector.java

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -65,25 +65,15 @@ public static void removePaperChannelInitializeListener() throws ReflectiveOpera
6565
}
6666

6767
private static boolean hasServerProtocolMethod() {
68-
try {
69-
Class.forName("org.bukkit.UnsafeValues").getDeclaredMethod("getProtocolVersion");
70-
return true;
71-
} catch (final ClassNotFoundException | NoSuchMethodException e) {
72-
return false;
73-
}
68+
return hasMethod("org.bukkit.UnsafeValues", "getProtocolVersion");
7469
}
7570

7671
private static boolean hasPaperInjectionMethod() {
7772
return hasClass("io.papermc.paper.network.ChannelInitializeListener");
7873
}
7974

8075
private static boolean hasIsStoppingMethod() {
81-
try {
82-
Bukkit.class.getDeclaredMethod("isStopping");
83-
return true;
84-
} catch (final NoSuchMethodException e) {
85-
return false;
86-
}
76+
return hasMethod(Bukkit.class, "isStopping");
8777
}
8878

8979
private static boolean hasPacketLimiter() {
@@ -98,4 +88,22 @@ public static boolean hasClass(final String className) {
9888
return false;
9989
}
10090
}
91+
92+
public static boolean hasMethod(final String className, final String method) {
93+
try {
94+
Class.forName(className).getDeclaredMethod(method);
95+
return true;
96+
} catch (final ClassNotFoundException | NoSuchMethodException e) {
97+
return false;
98+
}
99+
}
100+
101+
public static boolean hasMethod(final Class<?> clazz, final String method) {
102+
try {
103+
clazz.getDeclaredMethod(method);
104+
return true;
105+
} catch (final NoSuchMethodException e) {
106+
return false;
107+
}
108+
}
101109
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
3+
* Copyright (C) 2016-2024 ViaVersion and contributors
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package com.viaversion.viaversion.bukkit.providers;
19+
20+
import com.viaversion.viaversion.ViaVersionPlugin;
21+
import com.viaversion.viaversion.api.connection.UserConnection;
22+
import com.viaversion.viaversion.api.minecraft.BlockPosition;
23+
import com.viaversion.viaversion.bukkit.platform.PaperViaInjector;
24+
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.provider.PickItemProvider;
25+
import java.util.UUID;
26+
import org.bukkit.Bukkit;
27+
import org.bukkit.GameMode;
28+
import org.bukkit.Location;
29+
import org.bukkit.Material;
30+
import org.bukkit.block.Block;
31+
import org.bukkit.entity.Entity;
32+
import org.bukkit.entity.Player;
33+
import org.bukkit.inventory.ItemFactory;
34+
import org.bukkit.inventory.ItemStack;
35+
import org.bukkit.inventory.PlayerInventory;
36+
import org.bukkit.inventory.meta.BlockStateMeta;
37+
import org.checkerframework.checker.nullness.qual.Nullable;
38+
39+
public final class BukkitPickItemProvider extends PickItemProvider {
40+
private static final boolean HAS_PLACEMENT_MATERIAL_METHOD = PaperViaInjector.hasMethod("org.bukkit.block.BlockData", "getPlacementMaterial");
41+
private static final boolean HAS_SPAWN_EGG_METHOD = PaperViaInjector.hasMethod(ItemFactory.class, "getSpawnEgg");
42+
private static final double BLOCK_RANGE = 4.5 + 1;
43+
private static final double BLOCK_RANGE_SQUARED = BLOCK_RANGE * BLOCK_RANGE;
44+
private static final double ENTITY_RANGE = 3 + 3;
45+
private final ViaVersionPlugin plugin;
46+
47+
public BukkitPickItemProvider(final ViaVersionPlugin plugin) {
48+
this.plugin = plugin;
49+
}
50+
51+
@Override
52+
public void pickItemFromBlock(final UserConnection connection, final BlockPosition blockPosition, final boolean includeData) {
53+
final UUID uuid = connection.getProtocolInfo().getUuid();
54+
plugin.getServer().getScheduler().runTask(plugin, () -> {
55+
final Player player = plugin.getServer().getPlayer(uuid);
56+
if (player == null) {
57+
return;
58+
}
59+
60+
final Location playerLocation = player.getLocation();
61+
if (blockPosition.distanceFromCenterSquared(playerLocation.getX(), playerLocation.getY(), playerLocation.getZ()) > BLOCK_RANGE_SQUARED) {
62+
return;
63+
}
64+
65+
final Block block = player.getWorld().getBlockAt(blockPosition.x(), blockPosition.y(), blockPosition.z());
66+
if (block.getType() == Material.AIR) {
67+
return;
68+
}
69+
70+
final ItemStack item = blockToItem(block, includeData && player.getGameMode() == GameMode.CREATIVE);
71+
if (item != null) {
72+
pickItem(player, item);
73+
}
74+
});
75+
}
76+
77+
private @Nullable ItemStack blockToItem(final Block block, final boolean includeData) {
78+
if (HAS_PLACEMENT_MATERIAL_METHOD) {
79+
final ItemStack item = new ItemStack(block.getBlockData().getPlacementMaterial(), 1);
80+
if (includeData && item.getItemMeta() instanceof final BlockStateMeta blockStateMeta) {
81+
blockStateMeta.setBlockState(block.getState());
82+
}
83+
return item;
84+
} else if (block.getType().isItem()) {
85+
return new ItemStack(block.getType(), 1);
86+
}
87+
return null;
88+
}
89+
90+
@Override
91+
public void pickItemFromEntity(final UserConnection connection, final int entityId, final boolean includeData) {
92+
if (!HAS_SPAWN_EGG_METHOD) {
93+
return;
94+
}
95+
96+
final UUID uuid = connection.getProtocolInfo().getUuid();
97+
plugin.getServer().getScheduler().runTask(plugin, () -> {
98+
final Player player = plugin.getServer().getPlayer(uuid);
99+
if (player == null) {
100+
return;
101+
}
102+
103+
final Entity entity = player.getWorld().getNearbyEntities(player.getLocation(), ENTITY_RANGE, ENTITY_RANGE, ENTITY_RANGE).stream()
104+
.filter(e -> e.getEntityId() == entityId)
105+
.findAny()
106+
.orElse(null);
107+
if (entity == null) {
108+
return;
109+
}
110+
111+
final Material spawnEggType = Bukkit.getItemFactory().getSpawnEgg(entity.getType());
112+
if (spawnEggType != null) {
113+
pickItem(player, new ItemStack(spawnEggType, 1));
114+
}
115+
});
116+
}
117+
118+
private void pickItem(final Player player, final ItemStack item) {
119+
// Find matching item
120+
final PlayerInventory inventory = player.getInventory();
121+
final ItemStack[] contents = inventory.getStorageContents();
122+
int sourceSlot = -1;
123+
for (int i = 0; i < contents.length; i++) {
124+
final ItemStack content = contents[i];
125+
if (content == null || !content.isSimilar(item)) {
126+
continue;
127+
}
128+
129+
sourceSlot = i;
130+
break;
131+
}
132+
133+
if (sourceSlot != -1) {
134+
moveToHotbar(inventory, sourceSlot, contents);
135+
} else if (player.getGameMode() == GameMode.CREATIVE) {
136+
spawnItem(item, inventory, contents);
137+
}
138+
}
139+
140+
private void spawnItem(final ItemStack item, final PlayerInventory inventory, final ItemStack[] contents) {
141+
final int targetSlot = findEmptyHotbarSlot(inventory, inventory.getHeldItemSlot());
142+
inventory.setHeldItemSlot(targetSlot);
143+
final ItemStack heldItem = inventory.getItem(targetSlot);
144+
int emptySlot = targetSlot;
145+
if (heldItem != null && heldItem.getType() != Material.AIR) {
146+
// Swap to the first free slot in the inventory, else add it to the current hotbar slot
147+
for (int i = 0; i < contents.length; i++) {
148+
if (contents[i] == null || contents[i].getType() == Material.AIR) {
149+
emptySlot = i;
150+
break;
151+
}
152+
}
153+
}
154+
155+
inventory.setItem(emptySlot, heldItem);
156+
inventory.setItemInMainHand(item);
157+
}
158+
159+
private void moveToHotbar(final PlayerInventory inventory, final int sourceSlot, final ItemStack[] contents) {
160+
if (sourceSlot <= 9) {
161+
inventory.setHeldItemSlot(sourceSlot);
162+
return;
163+
}
164+
165+
final int heldSlot = inventory.getHeldItemSlot();
166+
final int targetSlot = findEmptyHotbarSlot(inventory, heldSlot);
167+
inventory.setHeldItemSlot(targetSlot);
168+
final ItemStack heldItem = inventory.getItem(targetSlot);
169+
inventory.setItemInMainHand(contents[sourceSlot]);
170+
inventory.setItem(sourceSlot, heldItem);
171+
}
172+
173+
private int findEmptyHotbarSlot(final PlayerInventory inventory, final int heldSlot) {
174+
for (int i = 0; i < 9; i++) {
175+
final ItemStack item = inventory.getItem(i);
176+
if (item == null || item.getType() == Material.AIR) {
177+
return i;
178+
}
179+
}
180+
return heldSlot;
181+
}
182+
}

common/src/main/java/com/viaversion/viaversion/protocols/v1_21_2to1_21_4/Protocol1_21_2To1_21_4.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.viaversion.viaversion.api.minecraft.Particle;
2424
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
2525
import com.viaversion.viaversion.api.minecraft.entities.EntityTypes1_21_4;
26+
import com.viaversion.viaversion.api.platform.providers.ViaProviders;
2627
import com.viaversion.viaversion.api.protocol.AbstractProtocol;
2728
import com.viaversion.viaversion.api.protocol.packet.provider.PacketTypesProvider;
2829
import com.viaversion.viaversion.api.protocol.packet.provider.SimplePacketTypesProvider;
@@ -35,6 +36,7 @@
3536
import com.viaversion.viaversion.protocols.v1_20_5to1_21.packet.ClientboundConfigurationPackets1_21;
3637
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPacket1_21_4;
3738
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPackets1_21_4;
39+
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.provider.PickItemProvider;
3840
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.rewriter.BlockItemPacketRewriter1_21_4;
3941
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.rewriter.ComponentRewriter1_21_4;
4042
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.rewriter.EntityPacketRewriter1_21_4;
@@ -190,6 +192,11 @@ protected void onMappingDataLoaded() {
190192
super.onMappingDataLoaded();
191193
}
192194

195+
@Override
196+
public void register(final ViaProviders providers) {
197+
providers.register(PickItemProvider.class, new PickItemProvider());
198+
}
199+
193200
@Override
194201
public void init(final UserConnection connection) {
195202
addEntityTracker(connection, new EntityTrackerBase(connection, EntityTypes1_21_4.PLAYER));
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
3+
* Copyright (C) 2016-2024 ViaVersion and contributors
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package com.viaversion.viaversion.protocols.v1_21_2to1_21_4.provider;
19+
20+
import com.viaversion.viaversion.api.connection.UserConnection;
21+
import com.viaversion.viaversion.api.minecraft.BlockPosition;
22+
import com.viaversion.viaversion.api.platform.providers.Provider;
23+
24+
public class PickItemProvider implements Provider {
25+
26+
public void pickItemFromBlock(final UserConnection connection, final BlockPosition blockPosition, final boolean includeData) {
27+
}
28+
29+
public void pickItemFromEntity(final UserConnection connection, final int entityId, final boolean includeData) {
30+
}
31+
}

common/src/main/java/com/viaversion/viaversion/protocols/v1_21_2to1_21_4/rewriter/BlockItemPacketRewriter1_21_4.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919

2020
import com.viaversion.nbt.tag.CompoundTag;
2121
import com.viaversion.nbt.tag.IntTag;
22+
import com.viaversion.viaversion.api.Via;
2223
import com.viaversion.viaversion.api.connection.UserConnection;
24+
import com.viaversion.viaversion.api.minecraft.BlockPosition;
2325
import com.viaversion.viaversion.api.minecraft.data.StructuredDataContainer;
2426
import com.viaversion.viaversion.api.minecraft.data.StructuredDataKey;
2527
import com.viaversion.viaversion.api.minecraft.item.Item;
@@ -31,6 +33,7 @@
3133
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.Protocol1_21_2To1_21_4;
3234
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPacket1_21_4;
3335
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.packet.ServerboundPackets1_21_4;
36+
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.provider.PickItemProvider;
3437
import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPacket1_21_2;
3538
import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2;
3639
import com.viaversion.viaversion.rewriter.BlockRewriter;
@@ -61,8 +64,18 @@ public void registerPackets() {
6164
wrapper.write(Types.VAR_INT, (int) slot);
6265
});
6366

64-
protocol.cancelServerbound(ServerboundPackets1_21_4.PICK_ITEM_FROM_BLOCK);
65-
protocol.cancelServerbound(ServerboundPackets1_21_4.PICK_ITEM_FROM_ENTITY);
67+
protocol.registerServerbound(ServerboundPackets1_21_4.PICK_ITEM_FROM_BLOCK, null, wrapper -> {
68+
final BlockPosition blockPosition = wrapper.read(Types.BLOCK_POSITION1_14);
69+
final boolean includeData = wrapper.read(Types.BOOLEAN);
70+
Via.getManager().getProviders().get(PickItemProvider.class).pickItemFromBlock(wrapper.user(), blockPosition, includeData);
71+
wrapper.cancel();
72+
});
73+
protocol.registerServerbound(ServerboundPackets1_21_4.PICK_ITEM_FROM_ENTITY, null, wrapper -> {
74+
final int entityId = wrapper.read(Types.VAR_INT);
75+
final boolean includeData = wrapper.read(Types.BOOLEAN);
76+
Via.getManager().getProviders().get(PickItemProvider.class).pickItemFromEntity(wrapper.user(), entityId, includeData);
77+
wrapper.cancel();
78+
});
6679

6780
protocol.registerClientbound(ClientboundPackets1_21_2.SET_CURSOR_ITEM, this::passthroughClientboundItem);
6881
registerSetPlayerInventory(ClientboundPackets1_21_2.SET_PLAYER_INVENTORY);

0 commit comments

Comments
 (0)