diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index ff1f0ac61..539344bfa 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -1122,34 +1122,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P this.eyeHeight = eyeHeight; } - /** - * Gets if this entity is in the same chunk as the specified position. - * - * @param position the checked position chunk - * @return true if the entity is in the same chunk as {@code position} - */ - public boolean sameChunk(@NotNull Position position) { - Check.notNull(position, "Position cannot be null"); - final Position pos = getPosition(); - final int chunkX1 = ChunkUtils.getChunkCoordinate((int) Math.floor(pos.getX())); - final int chunkZ1 = ChunkUtils.getChunkCoordinate((int) Math.floor(pos.getZ())); - - final int chunkX2 = ChunkUtils.getChunkCoordinate((int) Math.floor(position.getX())); - final int chunkZ2 = ChunkUtils.getChunkCoordinate((int) Math.floor(position.getZ())); - - return chunkX1 == chunkX2 && chunkZ1 == chunkZ2; - } - - /** - * Gets if the entity is in the same chunk as another. - * - * @param entity the entity to check - * @return true if both entities are in the same chunk, false otherwise - */ - public boolean sameChunk(@NotNull Entity entity) { - return sameChunk(entity.getPosition()); - } - /** * Removes the entity from the server immediately. *
diff --git a/src/main/java/net/minestom/server/entity/EntityManager.java b/src/main/java/net/minestom/server/entity/EntityManager.java index 05af5b7a8..97b48c88c 100644 --- a/src/main/java/net/minestom/server/entity/EntityManager.java +++ b/src/main/java/net/minestom/server/entity/EntityManager.java @@ -66,6 +66,7 @@ public final class EntityManager { waitingPlayer.init(); + // Spawn the player at Player#getRespawnPoint during the next instance tick spawningInstance.scheduleNextTick(waitingPlayer::setInstance); } } diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 968c82046..7896ba203 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -683,22 +683,19 @@ public class Player extends LivingEntity implements CommandSender { *
* Be aware that because chunk operations are expensive, * it is possible for this method to be non-blocking when retrieving chunks is required. - *
- * When this method is called for the first time (during player login), the player will be teleport at {@link #getRespawnPoint()}. * - * @param instance the new instance of the player + * @param instance the new player instance */ - @Override - public void setInstance(@NotNull Instance instance) { + public void setInstance(@NotNull Instance instance, @NotNull Position spawnPosition) { Check.notNull(instance, "instance cannot be null!"); Check.argCondition(this.instance == instance, "Instance should be different than the current one"); - final boolean firstSpawn = this.instance == null; // TODO: Handle player reconnections, must be false in that case too - // true if the chunks need to be sent to the client, can be false if the instances share the same chunks (eg SharedInstance) final boolean needWorldRefresh = !InstanceUtils.areLinked(this.instance, instance); if (needWorldRefresh) { + final boolean firstSpawn = this.instance == null; // TODO: Handle player reconnections, must be false in that case too + // Remove all previous viewable chunks (from the previous instance) for (Chunk viewableChunk : viewableChunks) { viewableChunk.removeViewer(this); @@ -712,13 +709,12 @@ public class Player extends LivingEntity implements CommandSender { } // Load all the required chunks - final Position pos = firstSpawn ? getRespawnPoint() : position; - final long[] visibleChunks = ChunkUtils.getChunksInRange(pos, getChunkRange()); + final long[] visibleChunks = ChunkUtils.getChunksInRange(spawnPosition, getChunkRange()); final ChunkCallback eachCallback = chunk -> { if (chunk != null) { - final int chunkX = ChunkUtils.getChunkCoordinate((int) pos.getX()); - final int chunkZ = ChunkUtils.getChunkCoordinate((int) pos.getZ()); + final int chunkX = ChunkUtils.getChunkCoordinate((int) spawnPosition.getX()); + final int chunkZ = ChunkUtils.getChunkCoordinate((int) spawnPosition.getZ()); if (chunk.getChunkX() == chunkX && chunk.getChunkZ() == chunkZ) { updateViewPosition(chunkX, chunkZ); @@ -728,7 +724,7 @@ public class Player extends LivingEntity implements CommandSender { final ChunkCallback endCallback = chunk -> { // This is the last chunk to be loaded , spawn player - spawnPlayer(instance, firstSpawn); + spawnPlayer(instance, spawnPosition, firstSpawn); }; // Chunk 0;0 always needs to be loaded @@ -739,26 +735,40 @@ public class Player extends LivingEntity implements CommandSender { } 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 - spawnPlayer(instance, false); + spawnPlayer(instance, spawnPosition, false); } } + /** + * Changes the player instance without changing its position (defaulted to {@link #getRespawnPoint()} + * if the player is not in any instance. + * + * @param instance the new player instance + * @see #setInstance(Instance, Position) + */ + @Override + public void setInstance(@NotNull Instance instance) { + setInstance(instance, this.instance != null ? getPosition() : getRespawnPoint()); + } + /** * Used to spawn the player once the client has all the required chunks. *
* Does add the player to {@code instance}, remove all viewable entities and call {@link PlayerSpawnEvent}. *
- * UNSAFE: only called with {@link #setInstance(Instance)}. + * UNSAFE: only called with {@link #setInstance(Instance, Position)}. * - * @param firstSpawn true if this is the player first spawn + * @param spawnPosition the position to teleport the player + * @param firstSpawn true if this is the player first spawn */ - private void spawnPlayer(@NotNull Instance instance, boolean firstSpawn) { + private void spawnPlayer(@NotNull Instance instance, @Nullable Position spawnPosition, boolean firstSpawn) { this.viewableEntities.forEach(entity -> entity.removeViewer(this)); super.setInstance(instance); - if (firstSpawn) { - teleport(getRespawnPoint()); + if (spawnPosition != null && !position.isSimilar(spawnPosition)) { + teleport(spawnPosition, + position.inSameChunk(spawnPosition) ? () -> refreshVisibleChunks(getChunk()) : null); } else { refreshVisibleChunks(getChunk()); } @@ -1519,7 +1529,7 @@ public class Player extends LivingEntity implements CommandSender { // New chunks indexes final long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(newChunk.toPosition(), getChunkRange()); - // Find the difference between the two arrays¬ + // Find the difference between the two arrays final int[] oldChunks = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunks, updatedVisibleChunks); final int[] newChunks = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunks, lastVisibleChunks); diff --git a/src/main/java/net/minestom/server/utils/Position.java b/src/main/java/net/minestom/server/utils/Position.java index 66b1d9978..4abc78674 100644 --- a/src/main/java/net/minestom/server/utils/Position.java +++ b/src/main/java/net/minestom/server/utils/Position.java @@ -1,5 +1,8 @@ package net.minestom.server.utils; +import net.minestom.server.utils.chunk.ChunkUtils; +import org.jetbrains.annotations.NotNull; + import java.util.Objects; /** @@ -72,8 +75,8 @@ public class Position { /** * Gets the distance between 2 positions. - * In cases where performance matters, {@link #getDistanceSquared(Position)} should be used - * as it does not perform the expensive Math.sqrt method. + * In cases where performance matters, {@link #getDistanceSquared(Position)} should be used + * as it does not perform the expensive Math.sqrt method. * * @param position the second position * @return the distance between {@code this} and {@code position} @@ -86,15 +89,15 @@ public class Position { /** * Gets the square distance to another position. - * - * @param position the second position - * @return the squared distance between {@code this} and {@code position} - */ - public float getDistanceSquared(Position position) { - return MathUtils.square(getX() - position.getX()) + - MathUtils.square(getY() - position.getY()) + - MathUtils.square(getZ() - position.getZ()); - } + * + * @param position the second position + * @return the squared distance between {@code this} and {@code position} + */ + public float getDistanceSquared(Position position) { + return MathUtils.square(getX() - position.getX()) + + MathUtils.square(getY() - position.getY()) + + MathUtils.square(getZ() - position.getZ()); + } /** * Gets a unit-vector pointing in the direction that this Location is @@ -209,7 +212,7 @@ public class Position { * @param position the position to compare * @return true if the two positions are similar */ - public boolean isSimilar(Position position) { + public boolean isSimilar(@NotNull Position position) { return Float.compare(position.x, x) == 0 && Float.compare(position.y, y) == 0 && Float.compare(position.z, z) == 0; @@ -221,11 +224,27 @@ public class Position { * @param position the position to compare * @return true if the two positions have the same view */ - public boolean hasSimilarView(Position position) { + public boolean hasSimilarView(@NotNull Position position) { return Float.compare(position.yaw, yaw) == 0 && Float.compare(position.pitch, pitch) == 0; } + /** + * Gets if two positions are in the same chunk. + * + * @param position the checked position chunk + * @return true if 'this' is in the same chunk as {@code position} + */ + public boolean inSameChunk(@NotNull Position position) { + final int chunkX1 = ChunkUtils.getChunkCoordinate((int) Math.floor(getX())); + final int chunkZ1 = ChunkUtils.getChunkCoordinate((int) Math.floor(getZ())); + + final int chunkX2 = ChunkUtils.getChunkCoordinate((int) Math.floor(position.getX())); + final int chunkZ2 = ChunkUtils.getChunkCoordinate((int) Math.floor(position.getZ())); + + return chunkX1 == chunkX2 && chunkZ1 == chunkZ2; + } + @Override public int hashCode() { return Objects.hash(x, y, z, yaw, pitch);