diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 13c64d911..b16342323 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -439,10 +439,11 @@ public class MinecraftServer { * Changes the chunk view distance of the server. * * @param chunkViewDistance the new chunk view distance - * @throws IllegalStateException if this is called after the server started + * @throws IllegalArgumentException if {@code chunkViewDistance} is not between 2 and 32 */ public static void setChunkViewDistance(int chunkViewDistance) { - Check.argCondition(!MathUtils.isBetween(chunkViewDistance, 2, 32), "The chunk view distance must be between 2 and 32"); + Check.argCondition(!MathUtils.isBetween(chunkViewDistance, 2, 32), + "The chunk view distance must be between 2 and 32"); MinecraftServer.chunkViewDistance = chunkViewDistance; if (started) { UpdateViewDistancePacket updateViewDistancePacket = new UpdateViewDistancePacket(); @@ -476,11 +477,20 @@ public class MinecraftServer { * WARNING: this need to be called before {@link #start(String, int, ResponseDataConsumer)}. * * @param entityViewDistance the new entity view distance - * @throws IllegalStateException if this is called after the server started + * @throws IllegalArgumentException if {@code entityViewDistance} is not between 0 and 32 */ public static void setEntityViewDistance(int entityViewDistance) { - Check.stateCondition(started, "The entity view distance cannot be changed after the server has been started."); + Check.argCondition(!MathUtils.isBetween(entityViewDistance, 0, 32), + "The entity view distance must be between 0 and 32"); MinecraftServer.entityViewDistance = entityViewDistance; + if (started) { + connectionManager.getOnlinePlayers().forEach(player -> { + final Chunk playerChunk = player.getChunk(); + if (playerChunk != null) { + player.refreshVisibleEntities(playerChunk); + } + }); + } } /** diff --git a/src/main/java/net/minestom/server/entity/Entity.java b/src/main/java/net/minestom/server/entity/Entity.java index f2b192f59..eaef06087 100644 --- a/src/main/java/net/minestom/server/entity/Entity.java +++ b/src/main/java/net/minestom/server/entity/Entity.java @@ -21,7 +21,6 @@ import net.minestom.server.instance.WorldBorder; import net.minestom.server.instance.block.CustomBlock; import net.minestom.server.network.packet.server.play.*; import net.minestom.server.thread.ThreadProvider; -import net.minestom.server.utils.ArrayUtils; import net.minestom.server.utils.BlockPosition; import net.minestom.server.utils.Position; import net.minestom.server.utils.Vector; @@ -84,7 +83,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { private boolean autoViewable; private final int id; private Data data; - private final Set viewers = new CopyOnWriteArraySet<>(); + protected final Set viewers = new CopyOnWriteArraySet<>(); protected UUID uuid; private boolean isActive; // False if entity has only been instanced without being added somewhere @@ -1007,7 +1006,12 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { instance.removeEntityFromChunk(this, lastChunk); instance.addEntityToChunk(this, newChunk); } - updateView(lastChunk, newChunk); + if (this instanceof Player) { + // Refresh player view + final Player player = (Player) this; + player.refreshVisibleChunks(newChunk); + player.refreshVisibleEntities(newChunk); + } } } } @@ -1020,72 +1024,6 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer { refreshPosition(position.getX(), position.getY(), position.getZ()); } - /** - * Manages viewable entities automatically if {@link #isAutoViewable()} is enabled. - *

- * Called by {@link #refreshPosition(float, float, float)} when the new position is in a different {@link Chunk}. - * - * @param lastChunk the previous {@link Chunk} of this entity - * @param newChunk the new {@link Chunk} of this entity - */ - private void updateView(@NotNull Chunk lastChunk, @NotNull Chunk newChunk) { - final boolean isPlayer = this instanceof Player; - - if (isPlayer) - ((Player) this).refreshVisibleChunks(newChunk); // Refresh loaded chunk - - // Refresh entity viewable list - final int entityViewDistance = MinecraftServer.getEntityViewDistance(); - final long[] lastVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()), entityViewDistance); - final long[] updatedVisibleChunksEntity = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), entityViewDistance); - - // Remove from previous chunks - final int[] oldChunksEntity = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunksEntity, updatedVisibleChunksEntity); - for (int index : oldChunksEntity) { - final long chunkIndex = lastVisibleChunksEntity[index]; - final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex); - final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex); - final Chunk chunk = instance.getChunk(chunkX, chunkZ); - if (chunk == null) - continue; - instance.getChunkEntities(chunk).forEach(ent -> { - if (ent instanceof Player) { - final Player player = (Player) ent; - if (isAutoViewable()) - removeViewer(player); - if (isPlayer) { - player.removeViewer((Player) this); - } - } else if (isPlayer) { - ent.removeViewer((Player) this); - } - }); - } - - // Add to new chunks - final int[] newChunksEntity = ArrayUtils.getDifferencesBetweenArray(updatedVisibleChunksEntity, lastVisibleChunksEntity); - for (int index : newChunksEntity) { - final long chunkIndex = updatedVisibleChunksEntity[index]; - final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex); - final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex); - final Chunk chunk = instance.getChunk(chunkX, chunkZ); - if (chunk == null) - continue; - instance.getChunkEntities(chunk).forEach(ent -> { - if (ent instanceof Player) { - Player player = (Player) ent; - if (isAutoViewable()) - addViewer(player); - if (this instanceof Player) { - player.addViewer((Player) this); - } - } else if (isPlayer) { - ent.addViewer((Player) this); - } - }); - } - } - /** * Updates the entity view internally. *

diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 11f6ba3e8..425a01b9c 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -80,6 +80,7 @@ public class Player extends LivingEntity implements CommandSender { private String username; protected final PlayerConnection playerConnection; + // All the entities that this player can see protected final Set viewableEntities = new CopyOnWriteArraySet<>(); private int latency; @@ -1406,12 +1407,12 @@ public class Player extends LivingEntity implements CommandSender { /** * Called when the player changes chunk (move from one to another). - * Can also be used to refresh the list of chunks that the client should see. + * Can also be used to refresh the list of chunks that the client should see based on {@link #getChunkRange()}. *

* It does remove and add the player from the chunks viewers list when removed or added. * It also calls the events {@link PlayerChunkUnloadEvent} and {@link PlayerChunkLoadEvent}. * - * @param newChunk the current/new player chunk + * @param newChunk the current/new player chunk (can be the current one) */ public void refreshVisibleChunks(@NotNull Chunk newChunk) { // Previous chunks indexes @@ -1420,7 +1421,7 @@ public class Player extends LivingEntity implements CommandSender { ).toArray(); // New chunks indexes - final long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()), getChunkRange()); + final long[] updatedVisibleChunks = ChunkUtils.getChunksInRange(newChunk.toPosition(), getChunkRange()); // Find the difference between the two arrays¬ final int[] oldChunks = ArrayUtils.getDifferencesBetweenArray(lastVisibleChunks, updatedVisibleChunks); @@ -1461,6 +1462,52 @@ public class Player extends LivingEntity implements CommandSender { } } + /** + * Refreshes the list of entities that the player should be able to see based on {@link MinecraftServer#getEntityViewDistance()} + * and {@link Entity#isAutoViewable()}. + * + * @param newChunk the new chunk of the player (can be the current one) + */ + public void refreshVisibleEntities(@NotNull Chunk newChunk) { + final int entityViewDistance = MinecraftServer.getEntityViewDistance(); + final float maximalDistance = entityViewDistance * Chunk.CHUNK_SECTION_SIZE; + + // Manage already viewable entities + this.viewableEntities.forEach(entity -> { + final float distance = entity.getDistance(this); + if (distance > maximalDistance) { + // Entity shouldn't be viewable anymore + if (isAutoViewable()) { + entity.removeViewer(this); + } + if (entity instanceof Player && entity.isAutoViewable()) { + removeViewer((Player) entity); + } + } + }); + + // Manage entities in unchecked chunks + final long[] chunksInRange = ChunkUtils.getChunksInRange(newChunk.toPosition(), entityViewDistance); + + for (long chunkIndex : chunksInRange) { + final int chunkX = ChunkUtils.getChunkCoordX(chunkIndex); + final int chunkZ = ChunkUtils.getChunkCoordZ(chunkIndex); + final Chunk chunk = instance.getChunk(chunkX, chunkZ); + if (chunk == null) + continue; + instance.getChunkEntities(chunk).forEach(entity -> { + if (isAutoViewable() && !entity.viewers.contains(this)) { + entity.addViewer(this); + } + + if (entity instanceof Player && entity.isAutoViewable()) { + addViewer((Player) entity); + } + }); + } + + } + @Override public void teleport(@NotNull Position position, @Nullable Runnable callback) { super.teleport(position, () -> { diff --git a/src/main/java/net/minestom/server/instance/Chunk.java b/src/main/java/net/minestom/server/instance/Chunk.java index 6f144b39b..dc0816e36 100644 --- a/src/main/java/net/minestom/server/instance/Chunk.java +++ b/src/main/java/net/minestom/server/instance/Chunk.java @@ -19,6 +19,7 @@ import net.minestom.server.network.packet.server.play.ChunkDataPacket; import net.minestom.server.network.packet.server.play.UpdateLightPacket; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.utils.MathUtils; +import net.minestom.server.utils.Position; import net.minestom.server.utils.binary.BinaryReader; import net.minestom.server.utils.chunk.ChunkCallback; import net.minestom.server.utils.chunk.ChunkSupplier; @@ -291,6 +292,15 @@ public abstract class Chunk implements Viewable, DataContainer { return chunkZ; } + /** + * Creates a {@link Position} object based on this chunk. + * + * @return the position of this chunk + */ + public Position toPosition() { + return new Position(CHUNK_SIZE_Z * getChunkX(), 0, CHUNK_SIZE_Z * getChunkZ()); + } + /** * Gets if this chunk will or had been loaded with a {@link ChunkGenerator}. *

diff --git a/src/test/java/demo/PlayerInit.java b/src/test/java/demo/PlayerInit.java index 1c1bd80cb..4705aecbd 100644 --- a/src/test/java/demo/PlayerInit.java +++ b/src/test/java/demo/PlayerInit.java @@ -7,6 +7,7 @@ import net.minestom.server.benchmark.BenchmarkManager; import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.*; import net.minestom.server.entity.damage.DamageType; +import net.minestom.server.entity.type.monster.EntityZombie; import net.minestom.server.event.entity.EntityAttackEvent; import net.minestom.server.event.item.ItemDropEvent; import net.minestom.server.event.item.PickupItemEvent; @@ -14,7 +15,6 @@ import net.minestom.server.event.player.*; import net.minestom.server.instance.Chunk; import net.minestom.server.instance.Instance; import net.minestom.server.instance.InstanceContainer; -import net.minestom.server.instance.SharedInstance; import net.minestom.server.instance.block.Block; import net.minestom.server.inventory.Inventory; import net.minestom.server.inventory.InventoryType; @@ -144,13 +144,8 @@ public class PlayerInit { Vector velocity = player.getPosition().copy().getDirection().multiply(6); itemEntity.setVelocity(velocity); - Instance instance = player.getInstance(); - InstanceContainer instanceContainer = instance instanceof InstanceContainer ? (InstanceContainer) instance : - ((SharedInstance) instance).getInstanceContainer(); - SharedInstance sharedInstance = MinecraftServer.getInstanceManager().createSharedInstance(instanceContainer); - player.setInstance(sharedInstance); - player.sendMessage("New instance"); - + EntityZombie entityZombie = new EntityZombie(player.getPosition()); + entityZombie.setInstance(player.getInstance()); }); player.addEventCallback(PlayerDisconnectEvent.class, event -> { diff --git a/src/test/java/demo/commands/TestCommand.java b/src/test/java/demo/commands/TestCommand.java index 66fdd970c..e1396cbea 100644 --- a/src/test/java/demo/commands/TestCommand.java +++ b/src/test/java/demo/commands/TestCommand.java @@ -1,5 +1,6 @@ package demo.commands; +import net.minestom.server.MinecraftServer; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.Arguments; import net.minestom.server.command.builder.Command; @@ -17,9 +18,7 @@ public class TestCommand extends Command { //addSyntax(this::execute, dynamicWord); } - Argument test = ArgumentType.Word("wordT"); - Argument testt = ArgumentType.Word("wordTt"); - Argument test2 = ArgumentType.StringArray("array"); + Argument test = ArgumentType.Integer("number"); setDefaultExecutor((source, args) -> { System.out.println("DEFAULT"); @@ -27,12 +26,10 @@ public class TestCommand extends Command { }); addSyntax((source, args) -> { - System.out.println(1); + int number = args.getInteger("number"); + source.sendMessage("set view to " + number); + MinecraftServer.setEntityViewDistance(number); }, test); - - addSyntax((source, args) -> { - System.out.println(2); - }, test, test2); } private void usage(CommandSender sender, Arguments arguments) {