diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java
index cbc3f25b8..87b539f61 100644
--- a/src/main/java/net/minestom/server/entity/Entity.java
+++ b/src/main/java/net/minestom/server/entity/Entity.java
@@ -683,8 +683,7 @@ public class Entity implements Viewable, Tickable, EventHandler, DataContainer,
* (performed using {@link #synchronizePosition()})
*
{@link EntityPositionAndRotationPacket} if {@code positionChange && viewChange}
* {@link EntityPositionPacket} if {@code positionChange}
- * {@link EntityRotationPacket} if {@code viewChange}
- * (performed using {@link #setView(float, float)})
+ * {@link EntityRotationPacket} and {@link EntityHeadLookPacket} if {@code viewChange}
*
* In case of a player's position and/or view change an additional {@link PlayerPositionAndLookPacket}
* is sent to self.
diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java
index 4ed2c950d..8a7ecad0c 100644
--- a/src/main/java/net/minestom/server/entity/Player.java
+++ b/src/main/java/net/minestom/server/entity/Player.java
@@ -69,7 +69,6 @@ import net.minestom.server.utils.entity.EntityUtils;
import net.minestom.server.utils.identity.NamedAndIdentified;
import net.minestom.server.utils.instance.InstanceUtils;
import net.minestom.server.utils.inventory.PlayerInventoryUtils;
-import net.minestom.server.utils.player.PlayerUtils;
import net.minestom.server.utils.time.Cooldown;
import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.utils.time.UpdateOption;
@@ -609,19 +608,13 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
sendDimension(instanceDimensionType);
}
- // Load all the required chunks
- final long[] visibleChunks = ChunkUtils.getChunksInRange(spawnPosition, getChunkRange());
+ // Only load the spawning chunk to speed up login, remaining chunks are loaded in #spawnPlayer
+ final long[] visibleChunks = ChunkUtils.getChunksInRange(spawnPosition, 0);
- final ChunkCallback endCallback = chunk -> {
- // This is the last chunk to be loaded , spawn player
- spawnPlayer(instance, spawnPosition, firstSpawn, true, dimensionChange);
- };
-
- // Chunk 0;0 always needs to be loaded
- instance.loadChunk(0, 0, chunk ->
- // Load all the required chunks
- ChunkUtils.optionalLoadAll(instance, visibleChunks, null, endCallback));
+ final ChunkCallback endCallback =
+ chunk -> spawnPlayer(instance, spawnPosition, firstSpawn, dimensionChange, true);
+ ChunkUtils.optionalLoadAll(instance, visibleChunks, null, endCallback);
} else {
// The player already has the good version of all the chunks.
// We just need to refresh his entity viewing list and add him to the instance
@@ -650,22 +643,20 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
*
* @param spawnPosition the position to teleport the player
* @param firstSpawn true if this is the player first spawn
+ * @param updateChunks true if chunks should be refreshed, false if the new instance shares the same
+ * chunks
*/
private void spawnPlayer(@NotNull Instance instance, @NotNull Position spawnPosition,
- boolean firstSpawn, boolean updateChunks, boolean dimensionChange) {
- // Clear previous instance elements
+ boolean firstSpawn, boolean dimensionChange, boolean updateChunks) {
if (!firstSpawn) {
+ // Player instance changed, clear current viewable collections
this.viewableChunks.forEach(chunk -> chunk.removeViewer(this));
this.viewableEntities.forEach(entity -> entity.removeViewer(this));
}
super.setInstance(instance, spawnPosition);
- if (!position.isSimilar(spawnPosition) && !firstSpawn) {
- // Player changed instance at a different position
- teleport(spawnPosition);
- } else if (updateChunks) {
- // Send newly visible chunks to player once spawned in the instance
+ if (updateChunks) {
refreshVisibleChunks();
}
@@ -1556,27 +1547,25 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
final int[] oldChunks = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunks, updatedVisibleChunks);
final int[] newChunks = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunks, lastVisibleChunks);
+ // Update client render distance
+ updateViewPosition(newChunk.getChunkX(), newChunk.getChunkZ());
+
// Unload old chunks
for (int index : oldChunks) {
final long chunkIndex = lastVisibleChunks[index];
final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex);
final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex);
- // TODO prevent the client from getting lag spikes when re-loading large chunks
- // Probably by having a distinction between visible and loaded (cache) chunks
- /*UnloadChunkPacket unloadChunkPacket = new UnloadChunkPacket();
+ final UnloadChunkPacket unloadChunkPacket = new UnloadChunkPacket();
unloadChunkPacket.chunkX = chunkX;
unloadChunkPacket.chunkZ = chunkZ;
- playerConnection.sendPacket(unloadChunkPacket);*/
+ playerConnection.sendPacket(unloadChunkPacket);
final Chunk chunk = instance.getChunk(chunkX, chunkZ);
if (chunk != null)
chunk.removeViewer(this);
}
- // Update client render distance
- updateViewPosition(newChunk.getChunkX(), newChunk.getChunkZ());
-
// Load new chunks
for (int index : newChunks) {
final long chunkIndex = updatedVisibleChunks[index];
@@ -2372,17 +2361,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
* based on which one is the lowest
*/
public int getChunkRange() {
- final int playerRange = getSettings().viewDistance;
- if (playerRange < 1) {
- // Didn't receive settings packet yet (is the case on login)
- // In this case we send an arbitrary number of chunks
- // Will be updated in PlayerSettings#refresh.
- // Non-compliant clients might also be stuck with this view
- return 7;
- } else {
- final int serverRange = MinecraftServer.getChunkViewDistance();
- return Math.min(playerRange, serverRange);
- }
+ return Math.min(getSettings().viewDistance, MinecraftServer.getChunkViewDistance());
}
/**
@@ -2614,6 +2593,10 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
private byte displayedSkinParts;
private MainHand mainHand;
+ public PlayerSettings() {
+ viewDistance = 2;
+ }
+
/**
* The player game language.
*
diff --git a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java
index eeefd3b64..22be4d414 100644
--- a/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java
+++ b/src/main/java/net/minestom/server/utils/chunk/ChunkUtils.java
@@ -161,18 +161,42 @@ public final class ChunkUtils {
* @param range how far should it retrieves chunk
* @return an array containing chunks index
*/
- @NotNull
- public static long[] getChunksInRange(@NotNull Position position, int range) {
- range = range * 2;
- long[] visibleChunks = new long[MathUtils.square(range + 1)];
- final int startLoop = -(range / 2);
- final int endLoop = range / 2 + 1;
- int counter = 0;
- for (int x = startLoop; x < endLoop; x++) {
- for (int z = startLoop; z < endLoop; z++) {
- final int chunkX = getChunkCoordinate(position.getX() + Chunk.CHUNK_SIZE_X * x);
- final int chunkZ = getChunkCoordinate(position.getZ() + Chunk.CHUNK_SIZE_Z * z);
- visibleChunks[counter++] = getChunkIndex(chunkX, chunkZ);
+ public static @NotNull long[] getChunksInRange(@NotNull Position position, int range) {
+ long[] visibleChunks = new long[MathUtils.square(range * 2 + 1)];
+ int xDistance = 0;
+ int xDirection = 1;
+ int zDistance = 0;
+ int zDirection = -1;
+ int len = 1;
+ int corner = 0;
+
+ for (int i = 0; i < visibleChunks.length; i++) {
+ final int chunkX = getChunkCoordinate(xDistance * Chunk.CHUNK_SIZE_X + position.getX());
+ final int chunkZ = getChunkCoordinate(zDistance * Chunk.CHUNK_SIZE_Z + position.getZ());
+ visibleChunks[i] = getChunkIndex(chunkX, chunkZ);
+
+ if (corner % 2 == 0) {
+ // step on X axis
+ xDistance += xDirection;
+
+ if (Math.abs(xDistance) == len) {
+ // hit corner
+ corner++;
+ xDirection = -xDirection;
+ }
+ } else {
+ // step on Z axis
+ zDistance += zDirection;
+
+ if (Math.abs(zDistance) == len) {
+ // hit corner
+ corner++;
+ zDirection = -zDirection;
+
+ if (corner % 4 == 0) {
+ len++;
+ }
+ }
}
}
return visibleChunks;