Implement block and entity picking

This commit is contained in:
Nassim Jahnke 2024-12-06 12:55:01 +01:00
parent 0c4dbbe0bb
commit 3cd01978df
No known key found for this signature in database
GPG Key ID: EF6771C01F6EF02F
7 changed files with 271 additions and 17 deletions

View File

@ -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;
}

View File

@ -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() {

View File

@ -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;
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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<ClientboundPa
super.onMappingDataLoaded();
}
@Override
public void register(final ViaProviders providers) {
providers.register(PickItemProvider.class, new PickItemProvider());
}
@Override
public void init(final UserConnection connection) {
addEntityTracker(connection, new EntityTrackerBase(connection, EntityTypes1_21_4.PLAYER));

View File

@ -0,0 +1,31 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
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) {
}
}

View File

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