From d8007dc73c28adb04f80bca500f660c8a8002288 Mon Sep 17 00:00:00 2001 From: Pablo Herrera Date: Wed, 11 Oct 2023 15:29:45 +0200 Subject: [PATCH] Fix valid block placements being prevented (#3407) --- .../protocol1_9to1_8/PaperPatch.java | 69 ++++++---- .../bukkit/util/CollisionChecker.java | 127 ++++++++++++++++++ .../viaversion/bukkit/util/NMSUtil.java | 0 .../bukkit/util/ProtocolSupportUtil.java | 0 4 files changed, 169 insertions(+), 27 deletions(-) create mode 100644 bukkit-legacy/src/main/java/com/viaversion/viaversion/bukkit/util/CollisionChecker.java rename {bukkit => bukkit-legacy}/src/main/java/com/viaversion/viaversion/bukkit/util/NMSUtil.java (100%) rename {bukkit => bukkit-legacy}/src/main/java/com/viaversion/viaversion/bukkit/util/ProtocolSupportUtil.java (100%) diff --git a/bukkit-legacy/src/main/java/com/viaversion/viaversion/bukkit/listeners/protocol1_9to1_8/PaperPatch.java b/bukkit-legacy/src/main/java/com/viaversion/viaversion/bukkit/listeners/protocol1_9to1_8/PaperPatch.java index 6a475b59f..7fe5d5a8e 100644 --- a/bukkit-legacy/src/main/java/com/viaversion/viaversion/bukkit/listeners/protocol1_9to1_8/PaperPatch.java +++ b/bukkit-legacy/src/main/java/com/viaversion/viaversion/bukkit/listeners/protocol1_9to1_8/PaperPatch.java @@ -18,6 +18,7 @@ package com.viaversion.viaversion.bukkit.listeners.protocol1_9to1_8; import com.viaversion.viaversion.bukkit.listeners.ViaBukkitListener; +import com.viaversion.viaversion.bukkit.util.CollisionChecker; import com.viaversion.viaversion.protocols.protocol1_9to1_8.Protocol1_9To1_8; import org.bukkit.Location; import org.bukkit.Material; @@ -30,6 +31,8 @@ import org.bukkit.plugin.Plugin; public class PaperPatch extends ViaBukkitListener { + private final CollisionChecker CHECKER = CollisionChecker.getInstance(); + public PaperPatch(Plugin plugin) { super(plugin, Protocol1_9To1_8.class); } @@ -43,34 +46,42 @@ public class PaperPatch extends ViaBukkitListener { @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) public void onPlace(BlockPlaceEvent e) { - if (isOnPipe(e.getPlayer())) { - Material block = e.getBlockPlaced().getType(); - if (isPlacable(block)) { + if (!isOnPipe(e.getPlayer())) return; + + if (CHECKER != null) { + Boolean intersect = CHECKER.intersects(e.getBlockPlaced(), e.getPlayer()); + if (intersect != null) { + if (intersect) e.setCancelled(true); return; } - Location location = e.getPlayer().getLocation(); - Block locationBlock = location.getBlock(); + } - if (locationBlock.equals(e.getBlock())) { + Material block = e.getBlockPlaced().getType(); + if (isPlacable(block)) { + return; + } + Location location = e.getPlayer().getLocation(); + Block locationBlock = location.getBlock(); + + if (locationBlock.equals(e.getBlock())) { + e.setCancelled(true); + } else { + if (locationBlock.getRelative(BlockFace.UP).equals(e.getBlock())) { e.setCancelled(true); } else { - if (locationBlock.getRelative(BlockFace.UP).equals(e.getBlock())) { - e.setCancelled(true); - } else { - Location diff = location.clone().subtract(e.getBlock().getLocation().add(0.5D, 0, 0.5D)); - // Within radius of block - if (Math.abs(diff.getX()) <= 0.8 && Math.abs(diff.getZ()) <= 0.8D) { - // Are they on the edge / shifting ish - if (diff.getY() <= 0.1D && diff.getY() >= -0.1D) { + Location diff = location.clone().subtract(e.getBlock().getLocation().add(0.5D, 0, 0.5D)); + // Within radius of block + if (Math.abs(diff.getX()) <= 0.8 && Math.abs(diff.getZ()) <= 0.8D) { + // Are they on the edge / shifting ish + if (diff.getY() <= 0.1D && diff.getY() >= -0.1D) { + e.setCancelled(true); + return; + } + BlockFace relative = e.getBlockAgainst().getFace(e.getBlock()); + // Are they towering up, (handles some latency) + if (relative == BlockFace.UP) { + if (diff.getY() < 1D && diff.getY() >= 0D) { e.setCancelled(true); - return; - } - BlockFace relative = e.getBlockAgainst().getFace(e.getBlock()); - // Are they towering up, (handles some latency) - if (relative == BlockFace.UP) { - if (diff.getY() < 1D && diff.getY() >= 0D) { - e.setCancelled(true); - } } } } @@ -80,12 +91,16 @@ public class PaperPatch extends ViaBukkitListener { private boolean isPlacable(Material material) { if (!material.isSolid()) return true; - // signs and banners + // signs, pressure plates, doors, and banners switch (material.getId()) { - case 63: - case 68: - case 176: - case 177: + case 63: // SIGN_POST + case 68: // WALL_SIGN + case 70: // STONE_PLATE + case 72: // WOOD_PLATE + case 147: // GOLD_PLATE + case 148: // IRON_PLATE + case 176: // STANDING_BANNER + case 177: // WALL_BANNER return true; default: return false; diff --git a/bukkit-legacy/src/main/java/com/viaversion/viaversion/bukkit/util/CollisionChecker.java b/bukkit-legacy/src/main/java/com/viaversion/viaversion/bukkit/util/CollisionChecker.java new file mode 100644 index 000000000..ac525b913 --- /dev/null +++ b/bukkit-legacy/src/main/java/com/viaversion/viaversion/bukkit/util/CollisionChecker.java @@ -0,0 +1,127 @@ +/* + * This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion + * Copyright (C) 2023 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.util; + +import com.viaversion.viaversion.api.Via; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; + +import java.lang.reflect.Method; +import java.util.AbstractList; +import java.util.List; +import java.util.logging.Level; + +public class CollisionChecker { + private static final CollisionChecker INSTANCE; + static { + CollisionChecker instance = null; + try { + instance = new CollisionChecker(); + } catch (ReflectiveOperationException ex) { + Via.getPlatform().getLogger().log( + Level.WARNING, + "Couldn't find reflection methods/fields to calculate bounding boxes.\n" + + "Placing non-full blocks where the player stands may fail.", ex); + } + INSTANCE = instance; + } + + private final Method GET_ENTITY_HANDLE; + private final Method GET_ENTITY_BB; + private final Method GET_BLOCK_BY_ID; + private final Method GET_WORLD_HANDLE; + private final Method GET_BLOCK_TYPE; + private final Method GET_COLLISIONS; + + private final Method SET_POSITION; + private final Object BLOCK_POSITION; + + private CollisionChecker() throws ReflectiveOperationException { + Class blockPosition = NMSUtil.nms("BlockPosition"); + Class mutableBlockPosition = NMSUtil.nms("BlockPosition$MutableBlockPosition"); + Class world = NMSUtil.nms("World"); + + GET_ENTITY_HANDLE = NMSUtil.obc("entity.CraftEntity").getDeclaredMethod("getHandle"); + GET_ENTITY_BB = GET_ENTITY_HANDLE.getReturnType().getDeclaredMethod("getBoundingBox"); + + GET_WORLD_HANDLE = NMSUtil.obc("CraftWorld").getDeclaredMethod("getHandle"); + GET_BLOCK_TYPE = world.getDeclaredMethod("getType", blockPosition); + + GET_BLOCK_BY_ID = NMSUtil.nms("Block").getDeclaredMethod("getById", int.class); + + GET_COLLISIONS = GET_BLOCK_BY_ID.getReturnType().getDeclaredMethod("a", + world, + blockPosition, + GET_BLOCK_TYPE.getReturnType(), + GET_ENTITY_BB.getReturnType(), + List.class, + GET_ENTITY_HANDLE.getReturnType()); + + SET_POSITION = mutableBlockPosition.getDeclaredMethod("c", int.class, int.class, int.class); + BLOCK_POSITION = mutableBlockPosition.getConstructor().newInstance(); + } + + public static CollisionChecker getInstance() { + return INSTANCE; + } + + public Boolean intersects(Block block, Entity entity) { + try { + Object nmsPlayer = GET_ENTITY_HANDLE.invoke(entity); + + Object nmsBlock = GET_BLOCK_BY_ID.invoke(null, block.getType().getId()); + Object nmsWorld = GET_WORLD_HANDLE.invoke(block.getWorld()); + + SET_POSITION.invoke(BLOCK_POSITION, block.getX(), block.getY(), block.getZ()); + + // Dummy list to avoid saving actual collision BB, we only care about if any collision happened + List collisions = new DummyList<>(); + + GET_COLLISIONS.invoke(nmsBlock, + nmsWorld, + BLOCK_POSITION, + GET_BLOCK_TYPE.invoke(nmsWorld, BLOCK_POSITION), + GET_ENTITY_BB.invoke(nmsPlayer), + collisions, + nmsPlayer); + return !collisions.isEmpty(); + } catch (ReflectiveOperationException ex) { + return null; + } + } + + private static class DummyList extends AbstractList { + private boolean any = false; + + @Override + public T get(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public void add(int idx, T el) { + any = true; + } + + @Override + public int size() { + return any ? 1 : 0; + } + } + +} diff --git a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/util/NMSUtil.java b/bukkit-legacy/src/main/java/com/viaversion/viaversion/bukkit/util/NMSUtil.java similarity index 100% rename from bukkit/src/main/java/com/viaversion/viaversion/bukkit/util/NMSUtil.java rename to bukkit-legacy/src/main/java/com/viaversion/viaversion/bukkit/util/NMSUtil.java diff --git a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/util/ProtocolSupportUtil.java b/bukkit-legacy/src/main/java/com/viaversion/viaversion/bukkit/util/ProtocolSupportUtil.java similarity index 100% rename from bukkit/src/main/java/com/viaversion/viaversion/bukkit/util/ProtocolSupportUtil.java rename to bukkit-legacy/src/main/java/com/viaversion/viaversion/bukkit/util/ProtocolSupportUtil.java