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;