diff --git a/src/main/java/net/minestom/server/listener/UseEntityListener.java b/src/main/java/net/minestom/server/listener/UseEntityListener.java index 246844963..819c32ca2 100644 --- a/src/main/java/net/minestom/server/listener/UseEntityListener.java +++ b/src/main/java/net/minestom/server/listener/UseEntityListener.java @@ -1,7 +1,9 @@ package net.minestom.server.listener; import net.minestom.server.ServerFlag; +import net.minestom.server.collision.BoundingBox; 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.LivingEntity; @@ -20,8 +22,11 @@ public class UseEntityListener { return; if (ServerFlag.ENFORCE_INTERACTION_LIMIT) { - double range = Math.pow(player.getAttributeValue(Attribute.ENTITY_INTERACTION_RANGE) + 1, 2); // Add 1 additional block for people with less than stellar ping - if (player.getDistanceSquared(entity) > range) { + final double maxDistanceSquared = Math.pow(player.getAttributeValue(Attribute.ENTITY_INTERACTION_RANGE) + 1, 2); + + final double distSquared = getDistSquared(player, entity); + + if (distSquared > maxDistanceSquared) { return; } } @@ -36,4 +41,33 @@ public class UseEntityListener { EventDispatcher.call(new PlayerEntityInteractEvent(player, entity, interactAt.hand(), interactPosition)); } } -} + + private static double getDistSquared(Player player, Entity entity) { + final Pos playerPos = player.getPosition(); + final double eyeHeight = player.getEyeHeight(); + final double px = playerPos.x(); + final double py = playerPos.y() + eyeHeight; + final double pz = playerPos.z(); + + final BoundingBox box = entity.getBoundingBox(); + final double halfWidth = box.width() / 2; + final double height = box.height(); + final Pos entityPos = entity.getPosition(); + + final double minX = entityPos.x() - halfWidth; + final double maxX = entityPos.x() + halfWidth; + final double minY = entityPos.y(); + final double maxY = entityPos.y() + height; + final double minZ = entityPos.z() - halfWidth; + final double maxZ = entityPos.z() + halfWidth; + + final double clampX = Math.max(minX, Math.min(px, maxX)); + final double clampY = Math.max(minY, Math.min(py, maxY)); + final double clampZ = Math.max(minZ, Math.min(pz, maxZ)); + + final double dx = px - clampX; + final double dy = py - clampY; + final double dz = pz - clampZ; + return dx * dx + dy * dy + dz * dz; + } +} \ No newline at end of file diff --git a/src/test/java/net/minestom/server/listener/UseEntityListenerTest.java b/src/test/java/net/minestom/server/listener/UseEntityListenerTest.java new file mode 100644 index 000000000..8ddebdeb3 --- /dev/null +++ b/src/test/java/net/minestom/server/listener/UseEntityListenerTest.java @@ -0,0 +1,106 @@ +package net.minestom.server.listener; + +import net.minestom.server.coordinate.Pos; +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.EntityType; +import net.minestom.server.entity.Player; +import net.minestom.server.entity.PlayerHand; +import net.minestom.server.entity.attribute.Attribute; +import net.minestom.server.event.player.PlayerEntityInteractEvent; +import net.minestom.server.instance.Instance; +import net.minestom.server.network.packet.client.play.ClientInteractEntityPacket; +import net.minestom.testing.Env; +import net.minestom.testing.EnvTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +@EnvTest +public class UseEntityListenerTest { + + private Player player; + private Entity targetEntity; + private boolean eventWasCalled; + + @BeforeEach + public void setup(Env env) { + Instance instance = env.createFlatInstance(); + + player = env.createPlayer(instance, new Pos(0, 0, 0)); + player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(5.0); + + targetEntity = new Entity(EntityType.SLIME); + targetEntity.setInstance(instance, new Pos(2, 0, 2)).join(); + + eventWasCalled = false; + + player.eventNode().addListener(PlayerEntityInteractEvent.class, event -> { + if (event.getPlayer().equals(player) && event.getTarget().equals(targetEntity)) { + eventWasCalled = true; + } + }); + } + + @Test + public void testInteractionWithinRange() { + ClientInteractEntityPacket packet = new ClientInteractEntityPacket( + targetEntity.getEntityId(), + new ClientInteractEntityPacket.InteractAt(0, 0, 0, PlayerHand.MAIN), + false + ); + + UseEntityListener.useEntityListener(packet, player); + assertTrue(eventWasCalled, "Expected PlayerEntityInteractEvent to be called for nearby target"); + } + + @Test + public void testInteractionOutOfRange() { + player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(1.0); + + targetEntity.teleport(new Pos(10, 0, 10)).join(); + ClientInteractEntityPacket packet = new ClientInteractEntityPacket( + targetEntity.getEntityId(), + new ClientInteractEntityPacket.InteractAt(0, 0, 0, PlayerHand.MAIN), + false + ); + + eventWasCalled = false; + UseEntityListener.useEntityListener(packet, player); + assertFalse(eventWasCalled, "Expected PlayerEntityInteractEvent NOT to be called for out-of-range target"); + } + + @Test + public void testInteractionConsideringHitboxAndEyePosition() { + player.getAttribute(Attribute.ENTITY_INTERACTION_RANGE).setBaseValue(1.5); + + targetEntity.teleport(new Pos(1.6, 0, 0)).join(); + + ClientInteractEntityPacket packet = new ClientInteractEntityPacket( + targetEntity.getEntityId(), + new ClientInteractEntityPacket.InteractAt(0, 0, 0, PlayerHand.MAIN), + false + ); + + eventWasCalled = false; + UseEntityListener.useEntityListener(packet, player); + assertTrue(eventWasCalled, "Expected PlayerEntityInteractEvent to be called considering hitbox size and eye position"); + } + + + @Test + public void testInteractionConsideringEyeHeight() { + player.teleport(new Pos(0, 1.6, 0)).join(); + targetEntity.teleport(new Pos(0, 1.6, 2)).join(); + + ClientInteractEntityPacket packet = new ClientInteractEntityPacket( + targetEntity.getEntityId(), + new ClientInteractEntityPacket.InteractAt(0, 0, 0, PlayerHand.MAIN), + false + ); + + eventWasCalled = false; + UseEntityListener.useEntityListener(packet, player); + assertTrue(eventWasCalled, "Expected PlayerEntityInteractEvent to be called considering eye height"); + } +}