From bcfb4c6ea4d82228aca4336066efe7ab66460ad3 Mon Sep 17 00:00:00 2001 From: iam Date: Mon, 14 Mar 2022 14:07:29 -0400 Subject: [PATCH] Fix block placement below players (#767) --- .../server/collision/BlockCollision.java | 38 +++++++++++++++- .../server/collision/CollisionUtils.java | 5 +++ .../listener/BlockPlacementListener.java | 25 +---------- .../PlacementCollisionIntegrationTest.java | 44 +++++++++++++++++++ 4 files changed, 88 insertions(+), 24 deletions(-) create mode 100644 src/test/java/net/minestom/server/collision/PlacementCollisionIntegrationTest.java diff --git a/src/main/java/net/minestom/server/collision/BlockCollision.java b/src/main/java/net/minestom/server/collision/BlockCollision.java index a74d116e4..b43c9b956 100644 --- a/src/main/java/net/minestom/server/collision/BlockCollision.java +++ b/src/main/java/net/minestom/server/collision/BlockCollision.java @@ -3,11 +3,17 @@ package net.minestom.server.collision; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.EntityType; +import net.minestom.server.entity.metadata.other.ArmorStandMeta; +import net.minestom.server.instance.Instance; import net.minestom.server.instance.block.Block; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -final class BlockCollision { +import java.util.Collection; + +public final class BlockCollision { // Minimum move amount, minimum final velocity private static final double MIN_DELTA = 0.001; @@ -325,6 +331,36 @@ final class BlockCollision { Vec.ZERO, finalResult.collidedShapePosition, finalResult.blockType); } + static boolean canPlaceBlockAt(Instance instance, Point blockPos, Block b) { + final Collection entities = instance.getNearbyEntities(blockPos, 3); + + return entities + .stream() + .filter(entity -> entity.getEntityType() != EntityType.ITEM) + .filter(entity -> { + // Marker Armor Stands should not prevent block placement + if (entity.getEntityMeta() instanceof ArmorStandMeta armorStandMeta) { + return !armorStandMeta.isMarker(); + } + return true; + }) + .noneMatch(entity -> { + boolean intersects; + + if (entity.getEntityType() == EntityType.PLAYER) { + // Need to move player slightly away from block we're placing. + // If player is at block 40 we cannot place a block at block 39 with side length 1 because the block will be in [39, 40] + // For this reason we subtract a small amount from the player position + Point playerPos = entity.getPosition().add(entity.getPosition().sub(blockPos).mul(0.01)); + intersects = b.registry().collisionShape().intersectBox(playerPos.sub(blockPos), entity.getBoundingBox()); + } else { + intersects = b.registry().collisionShape().intersectBox(entity.getPosition().sub(blockPos), entity.getBoundingBox()); + } + + return intersects; + }); + } + /** * Check if a moving entity will collide with a block. Updates finalResult * diff --git a/src/main/java/net/minestom/server/collision/CollisionUtils.java b/src/main/java/net/minestom/server/collision/CollisionUtils.java index 60cb76e53..5e5b2cb73 100644 --- a/src/main/java/net/minestom/server/collision/CollisionUtils.java +++ b/src/main/java/net/minestom/server/collision/CollisionUtils.java @@ -1,5 +1,6 @@ package net.minestom.server.collision; +import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.Entity; @@ -41,6 +42,10 @@ public final class CollisionUtils { return handlePhysics(entity, entityVelocity, null); } + public static boolean canPlaceBlockAt(Instance instance, Point blockPos, Block b) { + return BlockCollision.canPlaceBlockAt(instance, blockPos, b); + } + /** * Applies world border collision. * diff --git a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java index c6a9f4635..092898530 100644 --- a/src/main/java/net/minestom/server/listener/BlockPlacementListener.java +++ b/src/main/java/net/minestom/server/listener/BlockPlacementListener.java @@ -1,12 +1,10 @@ package net.minestom.server.listener; import net.minestom.server.MinecraftServer; +import net.minestom.server.collision.CollisionUtils; import net.minestom.server.coordinate.Point; -import net.minestom.server.entity.Entity; -import net.minestom.server.entity.EntityType; import net.minestom.server.entity.GameMode; import net.minestom.server.entity.Player; -import net.minestom.server.entity.metadata.other.ArmorStandMeta; import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.player.PlayerBlockInteractEvent; import net.minestom.server.event.player.PlayerBlockPlaceEvent; @@ -26,8 +24,6 @@ import net.minestom.server.network.packet.server.play.BlockChangePacket; import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.validate.Check; -import java.util.Collection; - public class BlockPlacementListener { private static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager(); @@ -109,24 +105,7 @@ public class BlockPlacementListener { } final Block placedBlock = useMaterial.block(); - final Collection entities = instance.getNearbyEntities(placementPosition, 5); - - // Check if the player is trying to place a block in an entity - boolean intersectPlayer = placedBlock.registry().collisionShape().intersectBox(player.getPosition().sub(placementPosition), player.getBoundingBox()); - - boolean hasIntersect = intersectPlayer || entities - .stream() - .filter(entity -> entity.getEntityType() != EntityType.ITEM) - .filter(entity -> { - // Marker Armor Stands should not prevent block placement - if (entity.getEntityMeta() instanceof ArmorStandMeta armorStandMeta) { - return !armorStandMeta.isMarker(); - } - return true; - }) - .anyMatch(entity -> placedBlock.registry().collisionShape().intersectBox(entity.getPosition().sub(placementPosition), entity.getBoundingBox())); - - if (hasIntersect) { + if (!CollisionUtils.canPlaceBlockAt(instance, placementPosition, placedBlock)) { refresh(player, chunk); return; } diff --git a/src/test/java/net/minestom/server/collision/PlacementCollisionIntegrationTest.java b/src/test/java/net/minestom/server/collision/PlacementCollisionIntegrationTest.java new file mode 100644 index 000000000..abd5674f4 --- /dev/null +++ b/src/test/java/net/minestom/server/collision/PlacementCollisionIntegrationTest.java @@ -0,0 +1,44 @@ +package net.minestom.server.collision; + +import net.minestom.server.api.Env; +import net.minestom.server.api.EnvTest; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.EntityType; +import net.minestom.server.instance.block.Block; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@EnvTest +public class PlacementCollisionIntegrationTest { + + @Test + public void empty(Env env) { + var instance = env.createFlatInstance(); + assertTrue(BlockCollision.canPlaceBlockAt(instance, new Vec(0, 40, 0), Block.STONE)); + } + + @Test + public void entityBlock(Env env) { + var instance = env.createFlatInstance(); + new Entity(EntityType.ZOMBIE).setInstance(instance, new Pos(0, 40, 0)).join(); + assertFalse(BlockCollision.canPlaceBlockAt(instance, new Vec(0, 40, 0), Block.STONE)); + } + + @Test + public void slab(Env env) { + var instance = env.createFlatInstance(); + new Entity(EntityType.ZOMBIE).setInstance(instance, new Pos(0, 40.75, 0)).join(); + assertTrue(BlockCollision.canPlaceBlockAt(instance, new Vec(0, 40, 0), Block.STONE_SLAB)); + } + + @Test + public void belowPlayer(Env env) { + var instance = env.createFlatInstance(); + env.createPlayer(instance, new Pos(5.7, -8, 6.389)); + assertTrue(BlockCollision.canPlaceBlockAt(instance, new Vec(5, -9, 6), Block.STONE)); + } +}