Fix to issue #2470 (#2576)

* Calculates the squared distance from the player's eye position to the closest point on the entity's bounding box, this also improves the accuracy of interaction range enforcement (preventing interactions w/ entities outside the allowed range regardless of bounding box size)

* unit testing

* unit testing
This commit is contained in:
Nex 2025-01-03 00:32:40 +00:00 committed by GitHub
parent d8c5831f4e
commit 4d3154b68c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 143 additions and 3 deletions

View File

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

View File

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