Allow dynamic entity view distance change

This commit is contained in:
themode 2020-11-04 19:14:04 +01:00
parent ae1d089603
commit f85b2c4aad
6 changed files with 89 additions and 92 deletions

View File

@ -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);
}
});
}
}
/**

View File

@ -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<Player> viewers = new CopyOnWriteArraySet<>();
protected final Set<Player> 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.
* <p>
* 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.
* <p>

View File

@ -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<Entity> 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()}.
* <p>
* 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, () -> {

View File

@ -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}.
* <p>

View File

@ -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 -> {

View File

@ -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) {