mirror of
https://github.com/Minestom/Minestom.git
synced 2025-02-11 01:41:47 +01:00
fix: clamp player position to prevent overflow issues (#2587)
This commit is contained in:
parent
a556e5a8f2
commit
55bcfaa07b
@ -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;
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user