fix: clamp player position to prevent overflow issues (#2587)

This commit is contained in:
Matt Worzala 2025-01-10 18:44:02 -05:00 committed by GitHub
parent a556e5a8f2
commit 55bcfaa07b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 47 additions and 7 deletions

View File

@ -46,6 +46,7 @@ import net.minestom.server.timer.Schedulable;
import net.minestom.server.timer.Scheduler;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.utils.ArrayUtils;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.PacketViewableUtils;
import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.block.BlockIterator;
@ -79,6 +80,10 @@ import java.util.function.UnaryOperator;
*/
public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, EventHandler<EntityEvent>, Taggable,
HoverEventSource<ShowEntity>, Sound.Emitter, Shape, AcquirableSource<Entity> {
// This is somewhat arbitrary, but we don't want to hit the max int ever because it is very easy to
// overflow while working with a position at the max int (for example, looping over a bounding box)
private static final int MAX_COORDINATE = 2_000_000_000;
private static final AtomicInteger LAST_ENTITY_ID = new AtomicInteger();
// Certain entities should only have their position packets sent during synchronization
@ -96,7 +101,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
protected Instance instance;
protected Chunk currentChunk;
protected Pos position;
protected Pos position; // Should be updated by setPositionInternal only.
protected Pos previousPosition;
protected Pos lastSyncedPosition;
protected boolean onGround;
@ -202,6 +207,19 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
this(entityType, UUID.randomUUID());
}
protected void setPositionInternal(@NotNull Pos newPosition) {
if (newPosition.x() >= MAX_COORDINATE || newPosition.x() <= -MAX_COORDINATE ||
newPosition.y() >= MAX_COORDINATE || newPosition.y() <= -MAX_COORDINATE ||
newPosition.z() >= MAX_COORDINATE || newPosition.z() <= -MAX_COORDINATE) {
newPosition = newPosition.withCoord(
MathUtils.clamp(newPosition.x(), -MAX_COORDINATE, MAX_COORDINATE),
MathUtils.clamp(newPosition.y(), -MAX_COORDINATE, MAX_COORDINATE),
MathUtils.clamp(newPosition.z(), -MAX_COORDINATE, MAX_COORDINATE)
);
}
this.position = newPosition;
}
/**
* Schedules a task to be run during the next entity tick.
*
@ -325,7 +343,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
final Runnable endCallback = () -> {
this.previousPosition = this.position;
this.position = globalPosition;
setPositionInternal(globalPosition);
this.velocity = globalVelocity;
refreshCoordinate(globalPosition);
if (this instanceof Player player) player.synchronizePositionAfterTeleport(position, velocity, flags, shouldConfirm);
@ -356,7 +374,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
public void setView(float yaw, float pitch) {
final Pos currentPosition = this.position;
if (currentPosition.sameView(yaw, pitch)) return;
this.position = currentPosition.withView(yaw, pitch);
setPositionInternal(currentPosition.withView(yaw, pitch));
synchronizeView();
}
@ -784,7 +802,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
if (previousInstance != null) removeFromInstance(previousInstance);
this.isActive = true;
this.position = spawnPosition;
setPositionInternal(spawnPosition);
this.previousPosition = spawnPosition;
this.lastSyncedPosition = spawnPosition;
this.previousPhysicsResult = null;
@ -1236,7 +1254,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
final var previousPosition = this.position;
final Pos position = ignoreView ? previousPosition.withCoord(newPosition) : newPosition;
if (position.equals(lastSyncedPosition)) return;
this.position = position;
setPositionInternal(position);
this.previousPosition = previousPosition;
if (!position.samePoint(previousPosition)) refreshCoordinate(position);
if (nextSynchronizationTick <= ticks + 1 || !sendPackets) {
@ -1297,7 +1315,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
final Pos newPassengerPos = oldPassengerPos.withCoord(newPosition.x(),
newPosition.y() + EntityUtils.getPassengerHeightOffset(this, passenger),
newPosition.z());
passenger.position = newPassengerPos;
passenger.setPositionInternal(newPassengerPos);
passenger.previousPosition = oldPassengerPos;
passenger.refreshCoordinate(newPassengerPos);
}
@ -1474,7 +1492,7 @@ public class Entity implements Viewable, Tickable, Schedulable, Snapshotable, Ev
this.removed = true;
if (!permanent) {
// Reset some state to be ready for re-use
this.position = Pos.ZERO;
setPositionInternal(Pos.ZERO);
this.previousPosition = Pos.ZERO;
this.lastSyncedPosition = Pos.ZERO;
}

View File

@ -8,6 +8,11 @@ import net.minestom.testing.Env;
import net.minestom.testing.EnvTest;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.junit.jupiter.api.Assertions.assertEquals;
@EnvTest
@ -99,4 +104,21 @@ public class EntityTeleportIntegrationTest {
player.teleport(new Pos(5, 10, 2, 5, 5), null, RelativeFlags.VIEW).join();
assertEquals(player.getPosition(), new Pos(5, 10, 2, 95, 5));
}
@Test
public void entityTeleportToInfinity(Env env) throws ExecutionException, InterruptedException, TimeoutException {
var instance = env.createFlatInstance();
var entity = new Entity(EntityTypes.ZOMBIE);
entity.setInstance(instance, new Pos(0, 42, 0)).join();
assertEquals(instance, entity.getInstance());
assertEquals(new Pos(0, 42, 0), entity.getPosition());
entity.teleport(new Pos(Double.POSITIVE_INFINITY, 42, 52)).join();
CompletableFuture.runAsync(() -> entity.tick(System.currentTimeMillis()))
.get(10, TimeUnit.SECONDS);
// This should not hang forever
// The position should have been capped at 2 billion.
assertEquals(new Pos(2_000_000_000, 42, 52), entity.getPosition());
}
}