Files
AkarinMC/patches/server/0024-Tuinity-Per-player-view-distance-implementation.patch
ㄗㄠˋ ㄑㄧˊ 341aca2883 fix
2020-04-18 20:21:43 +08:00

1500 lines
78 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=84=97=E3=84=A0=CB=8B=20=E3=84=91=E3=84=A7=CB=8A?=
<tsao-chi@the-lingo.org>
Date: Sat, 18 Apr 2020 19:57:30 +0800
Subject: [PATCH] Tuinity Per player view distance implementation
This patch implements CraftPlayer#setViewDistance and
CraftPlayer#getViewDistance
This patch replaces the existing system to handle player tickets
and chunk sending to players.
Most of the work is pulled by two
PlayerAreaMap's controlling: a radius for sending chunks and a radius
for adding player tickets. Currently the difference between the two
radii is just 1.
Normal vanilla has a difference of two, but this
causes issues where outer chunks in the view distance sometimes
do not send. This is because the outer radius is not guaranteed to
be at ticket level 32, which is required to be sent to players.
The ticket manager in ChunkMapDistance for players is no longer used and
is entirely replaced by the ticket distance map. The ticket tracker also
adds a configurable chunk load rate per player.
This patch moves the order of the distance map update in movePlayer
until after a client is sent a center section packet. This is required
to avoid the client rejecting chunks it receives when teleporting.
diff --git a/src/main/java/io/akarin/server/Config.java b/src/main/java/io/akarin/server/Config.java
index 2ac8f02a97429f04f3e5c9206ec228edccaf24c9..13608380b24a52666c8238f109d8ab507e75debf 100644
--- a/src/main/java/io/akarin/server/Config.java
+++ b/src/main/java/io/akarin/server/Config.java
@@ -96,6 +96,42 @@ public final class Config {
return Config.config.getDouble(path, dfl);
}
+ public static double maxChunkSendsPerPlayer; // per second
+
+ public static int[] maxChunkSendsPerPlayerChoice = new int[100];
+
+ private static void maxChunkLoadsPerPlayer() {
+ maxChunkSendsPerPlayer = Config.getDouble("target-chunk-sends-per-player-per-second", 40.0);
+ if (maxChunkSendsPerPlayer <= -1.0) {
+ maxChunkSendsPerPlayer = Integer.MAX_VALUE;
+ } else if (maxChunkSendsPerPlayer <= 1.0) {
+ maxChunkSendsPerPlayer = 1.0;
+ } else if (maxChunkSendsPerPlayer > Integer.MAX_VALUE) {
+ maxChunkSendsPerPlayer = Integer.MAX_VALUE;
+ }
+
+ double rateTick = maxChunkSendsPerPlayer / 20.0;
+ double a = Math.floor(rateTick);
+ double b = Math.ceil(rateTick);
+
+ // we want to spread out a and b over the interval so it's smooth
+
+ int aInt = (int)a;
+ int bInt = (int)b;
+ double total = b;
+ maxChunkSendsPerPlayerChoice[0] = bInt;
+
+ for (int i = 1, len = maxChunkSendsPerPlayerChoice.length; i < len; ++i) {
+ if (total / (double)i >= rateTick) {
+ total += a;
+ maxChunkSendsPerPlayerChoice[i] = aInt;
+ } else {
+ total += b;
+ maxChunkSendsPerPlayerChoice[i] = bInt;
+ }
+ }
+ }
+
public static final class WorldConfig {
public final String worldName;
@@ -178,6 +214,14 @@ public final class Config {
this.worldDefaults.addDefault(path, Double.valueOf(dfl));
return this.config.getDouble(path, this.worldDefaults.getDouble(path));
}
+
+ public int noTickViewDistance;
+ public int maxPendingChunkLoads;
+ private void noTickViewDistance() {
+ this.noTickViewDistance = this.getInt("no-tick-view-distance", -1);
+ this.maxPendingChunkLoads = this.getInt("max-pending-chunk-loads", 1);
+ }
+
}
}
diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java
index 1890fad9ed10c514b7b5982d2c112f7dc52866a5..984767304e6b5270cc8f86785b755a02d78d72ed 100644
--- a/src/main/java/net/minecraft/server/ChunkMapDistance.java
+++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java
@@ -1,5 +1,6 @@
package net.minecraft.server;
+import net.minecraft.server.MCUtil; // Tuinity
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.mojang.datafixers.util.Either;
@@ -27,12 +28,12 @@ import org.apache.logging.log4j.Logger;
public abstract class ChunkMapDistance {
private static final Logger LOGGER = LogManager.getLogger();
- private static final int b = 33 + ChunkStatus.a(ChunkStatus.FULL) - 2;
+ private static final int b = 33 + ChunkStatus.a(ChunkStatus.FULL) - 2; public static int getPlayerTicketLevel() { return ChunkMapDistance.b; } // Tuinity - OBFHELPER
private final Long2ObjectMap<ObjectSet<EntityPlayer>> c = new Long2ObjectOpenHashMap();
public final Long2ObjectOpenHashMap<ArraySetSorted<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
private final ChunkMapDistance.a e = new ChunkMapDistance.a();
private final ChunkMapDistance.b f = new ChunkMapDistance.b(8);
- private final ChunkMapDistance.c g = new ChunkMapDistance.c(33);
+ //private final ChunkMapDistance.c g = new ChunkMapDistance.c(33); // Tuinity - no longer used
private final java.util.Queue<PlayerChunk> pendingChunkUpdates = new java.util.ArrayDeque<>(); // PAIL pendingChunkUpdates // Paper - use a queue // Akarin - backport Tuinity - use a better queue
private final ChunkTaskQueueSorter i;
private final Mailbox<ChunkTaskQueueSorter.a<Runnable>> j;
@@ -41,6 +42,32 @@ public abstract class ChunkMapDistance {
private final Executor m;
private long currentTick;
+ // Tuinity start
+ protected PlayerChunkMap chunkMap;
+ protected final ChunkMapDistance.TicketTracker playerTickViewDistanceHandler = new TicketTracker(ChunkMapDistance.getPlayerTicketLevel()) {
+ @Override
+ protected int tryQueueChunk(int chunkX, int chunkZ, EntityPlayer player) {
+ long coordinate = MCUtil.getCoordinateKey(chunkX, chunkZ);
+ PlayerChunk currentChunk = ChunkMapDistance.this.chunkMap.getUpdatingChunk(coordinate);
+ if (currentChunk != null) {
+ Chunk fullChunk = currentChunk.getFullReadyChunk();
+ if (fullChunk != null && fullChunk.areNeighboursLoaded(2)) {
+ this.chunkReferenceMap.putIfAbsent(coordinate, LOADED_PLAYER_REFERENCE);
+ ChunkMapDistance.this.addTicket(coordinate, new Ticket<>(TicketType.PLAYER, this.ticketLevel, new ChunkCoordIntPair(chunkX, chunkZ)));
+ return QUEUED;
+ }
+ }
+
+ return FAILED;
+ }
+
+ @Override
+ protected int getMaxChunkLoads(EntityPlayer player) {
+ return Integer.MAX_VALUE;
+ }
+ };
+ // Tuinity end
+
protected ChunkMapDistance(Executor executor, Executor executor1) {
executor1.getClass();
Mailbox<Runnable> mailbox = Mailbox.a("player ticket throttler", executor1::execute);
@@ -86,7 +113,7 @@ public abstract class ChunkMapDistance {
public boolean a(PlayerChunkMap playerchunkmap) {
this.f.a();
- this.g.a();
+ //this.g.a(); // Tuinity - no longer used
int i = Integer.MAX_VALUE - this.e.a(Integer.MAX_VALUE);
boolean flag = i != 0;
@@ -242,7 +269,7 @@ public abstract class ChunkMapDistance {
return new ObjectOpenHashSet();
})).add(entityplayer);
this.f.b(i, 0, true);
- this.g.b(i, 0, true);
+ //this.g.b(i, 0, true); // Tuinity - no longer used
}
public void b(SectionPosition sectionposition, EntityPlayer entityplayer) {
@@ -253,7 +280,7 @@ public abstract class ChunkMapDistance {
if (objectset.isEmpty()) {
this.c.remove(i);
this.f.b(i, Integer.MAX_VALUE, false);
- this.g.b(i, Integer.MAX_VALUE, false);
+ //this.g.b(i, Integer.MAX_VALUE, false); // Tuinity - no longer used
}
}
@@ -271,9 +298,16 @@ public abstract class ChunkMapDistance {
return s;
}
+ protected void setViewDistance(int viewDistance) { this.a(viewDistance); } // Tuinity - OBFHELPER
protected void a(int i) {
- this.g.a(i);
+ //this.g.a(i); // Tuinity - no longer used
}
+ // Tuinity start - per player view distance
+ protected void setGlobalViewDistance(int viewDistance, PlayerChunkMap chunkMap) {
+ this.chunkMap = chunkMap;
+ this.setViewDistance(viewDistance);
+ }
+ // Tuinity end
public int b() {
this.f.a();
@@ -354,6 +388,222 @@ public abstract class ChunkMapDistance {
}
}
+ // Tuinity start - Per player view distance
+ abstract class TicketTracker {
+
+ static final int LOADED_PLAYER_REFERENCE = -2;
+
+ protected final it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap chunkReferenceMap = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap(8192, 0.25f);
+ {
+ this.chunkReferenceMap.defaultReturnValue(-1);
+ }
+ protected final it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap lastLoadedRadiusByPlayer = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap(512, 0.5f);
+ {
+ this.lastLoadedRadiusByPlayer.defaultReturnValue(-1);
+ }
+
+ protected final it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap pendingChunkLoadsByPlayer = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap(512, 0.5f);
+ protected final it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap lastChunkPositionByPlayer = new it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap(512, 0.5f);
+ {
+ this.lastChunkPositionByPlayer.defaultReturnValue(Long.MIN_VALUE);
+ }
+
+ protected final int ticketLevel;
+
+ public TicketTracker(int ticketLevel) {
+ this.ticketLevel = ticketLevel;
+ }
+
+ protected final java.util.List<EntityPlayer> players = new java.util.ArrayList<>(256);
+
+ protected com.destroystokyo.paper.util.misc.PlayerAreaMap areaMap;
+
+ static final int ALREADY_QUEUED = 0;
+ static final int QUEUED = 1;
+ static final int FAILED = 2;
+
+ protected abstract int tryQueueChunk(int chunkX, int chunkZ, EntityPlayer player);
+
+ protected abstract int getMaxChunkLoads(EntityPlayer player);
+
+ public void tick() {
+ for (EntityPlayer player : this.players) {
+ int playerId = player.getId();
+ int lastLoadedRadius = this.lastLoadedRadiusByPlayer.get(playerId);
+ int pendingChunkLoads = this.pendingChunkLoadsByPlayer.get(playerId);
+ long lastChunkPos = this.lastChunkPositionByPlayer.get(playerId);
+ long currentChunkPos = this.areaMap.getLastCoordinate(player);
+
+ if (currentChunkPos == Long.MIN_VALUE) {
+ // not tracking for whatever reason...
+ continue;
+ }
+
+ int newX = MCUtil.getCoordinateX(currentChunkPos);
+ int newZ = MCUtil.getCoordinateZ(currentChunkPos);
+
+ // handle movement
+ if (currentChunkPos != lastChunkPos) {
+ this.lastChunkPositionByPlayer.put(playerId, currentChunkPos);
+ if (lastChunkPos != Long.MIN_VALUE) {
+ int oldX = MCUtil.getCoordinateX(lastChunkPos);
+ int oldZ = MCUtil.getCoordinateZ(lastChunkPos);
+
+ int radiusDiff = Math.max(Math.abs(newX - oldX), Math.abs(newZ - oldZ));
+ lastLoadedRadius = Math.max(-1, lastLoadedRadius - radiusDiff);
+ this.lastLoadedRadiusByPlayer.put(playerId, lastLoadedRadius);
+ }
+ }
+
+ int maxChunkLoads = this.getMaxChunkLoads(player);
+
+ int radius = lastLoadedRadius + 1;
+ int viewDistance = this.areaMap.getLastViewDistance(player);
+
+ if (radius > viewDistance) {
+ // distance map will unload our chunks
+ this.lastLoadedRadiusByPlayer.put(playerId, viewDistance);
+ continue;
+ }
+
+ if (pendingChunkLoads >= maxChunkLoads) {
+ continue;
+ }
+
+ radius_loop:
+ for (; radius <= viewDistance; ++radius) {
+ for (int offset = 0; offset <= radius; ++offset) {
+ // try to load the chunks closest to the player by distance
+ // so instead of going left->right on the x axis, we start at the center of the view distance square
+ // and go left and right at the same time
+
+ // try top 2 chunks
+ // top left
+ int attempt = 0;
+ if ((attempt = this.tryQueueChunk(newX - offset, newZ + radius, player)) == QUEUED) {
+ if (++pendingChunkLoads >= maxChunkLoads) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // top right
+ if ((attempt = this.tryQueueChunk(newX + offset, newZ + radius, player)) == QUEUED) {
+ if (++pendingChunkLoads >= maxChunkLoads) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // try bottom 2 chunks
+
+ // bottom left
+ if ((attempt = this.tryQueueChunk(newX - offset, newZ - radius, player)) == QUEUED) {
+ if (++pendingChunkLoads >= maxChunkLoads) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // bottom right
+ if ((attempt = this.tryQueueChunk(newX + offset, newZ - radius, player)) == QUEUED) {
+ if (++pendingChunkLoads >= maxChunkLoads) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // try left 2 chunks
+
+ // left down
+ if ((attempt = this.tryQueueChunk(newX - radius, newZ - offset, player)) == QUEUED) {
+ if (++pendingChunkLoads >= maxChunkLoads) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // left up
+ if ((attempt = this.tryQueueChunk(newX - radius, newZ + offset, player)) == QUEUED) {
+ if (++pendingChunkLoads >= maxChunkLoads) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // try right 2 chunks
+
+ // right down
+ if ((attempt = this.tryQueueChunk(newX + radius, newZ - offset, player)) == QUEUED) {
+ if (++pendingChunkLoads >= maxChunkLoads) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // right up
+ if ((attempt = this.tryQueueChunk(newX + radius, newZ + offset, player)) == QUEUED) {
+ if (++pendingChunkLoads >= maxChunkLoads) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+ }
+ }
+
+ int newLoadedRadius = radius - 1;
+ if (newLoadedRadius != lastLoadedRadius) {
+ this.lastLoadedRadiusByPlayer.put(playerId, newLoadedRadius);
+ }
+ this.pendingChunkLoadsByPlayer.put(playerId, pendingChunkLoads);
+ }
+ }
+
+ public void addPlayer(EntityPlayer player) {
+ this.players.add(player);
+ }
+
+ public void removePlayer(EntityPlayer player) {
+ this.players.remove(player);
+ this.lastLoadedRadiusByPlayer.remove(player.getId());
+ this.pendingChunkLoadsByPlayer.remove(player.getId());
+ this.lastChunkPositionByPlayer.remove(player.getId());
+ }
+
+ public void onChunkLoad(int chunkX, int chunkZ) {
+ long coordinate = MCUtil.getCoordinateKey(chunkX, chunkZ);
+ int playerReference = this.chunkReferenceMap.replace(coordinate, LOADED_PLAYER_REFERENCE);
+ if (playerReference != -1) {
+ this.pendingChunkLoadsByPlayer.computeIfPresent(playerReference, (Integer keyInMap, Integer valueInMap) -> {
+ return valueInMap - 1;
+ });
+ }
+ }
+
+ // this is invoked if and only if there are no other players in range of the chunk.
+ public void playerMoveOutOfRange(int chunkX, int chunkZ) {
+ long coordinate = MCUtil.getCoordinateKey(chunkX, chunkZ);
+ int playerReference = this.chunkReferenceMap.remove(coordinate);
+ if (playerReference != -1) {
+ if (playerReference != LOADED_PLAYER_REFERENCE) {
+ this.pendingChunkLoadsByPlayer.computeIfPresent(playerReference, (Integer keyInMap, Integer valueInMap) -> {
+ return valueInMap - 1;
+ });
+ }
+ ChunkMapDistance.this.removeTicket(coordinate, new Ticket<>(TicketType.PLAYER, this.ticketLevel, new ChunkCoordIntPair(chunkX, chunkZ)));
+ }
+ }
+ }
+ // Tuinity end - per player view distance
+
class c extends ChunkMapDistance.b {
private int e = 0;
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
index 6a2a6e671f230fd6a8adb5e6ccec22dede740f44..adc2010e643eb14ccedbb0c3af9ec7e02ab0daa2 100644
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
@@ -534,7 +534,7 @@ public class ChunkProviderServer extends IChunkProvider {
return this.serverThreadQueue.executeNext();
}
- private boolean tickDistanceManager() {
+ boolean tickDistanceManager() { // Tuinity - remove private
boolean flag = this.chunkMapDistance.a(this.playerChunkMap);
boolean flag1 = this.playerChunkMap.b();
@@ -643,6 +643,11 @@ public class ChunkProviderServer extends IChunkProvider {
this.world.getMinecraftServer().midTickLoadChunks(); // Paper
this.tickDistanceManager();
this.world.timings.doChunkMap.stopTiming(); // Spigot
+ // Tuinity start
+ this.playerChunkMap.chunkLoadScheduler.queueMaxLoads();
+ this.playerChunkMap.getChunkMapDistanceManager().playerTickViewDistanceHandler.tick();
+ this.playerChunkMap.chunkSendThrottler.tick();
+ // Tuinity end
// this.world.getMethodProfiler().exitEnter("chunks"); // Akarin - remove caller
this.world.timings.chunks.startTiming(); // Paper - timings
this.tickChunks();
diff --git a/src/main/java/net/minecraft/server/EntityEnderDragon.java b/src/main/java/net/minecraft/server/EntityEnderDragon.java
index 2887cb14e4f9f90412cbf6f83c651e5866fb16b3..b9b2b053878cc21245e6bbb2174dc51c35c8e624 100644
--- a/src/main/java/net/minecraft/server/EntityEnderDragon.java
+++ b/src/main/java/net/minecraft/server/EntityEnderDragon.java
@@ -579,9 +579,9 @@ public class EntityEnderDragon extends EntityInsentient implements IMonster {
// CraftBukkit start - Use relative location for far away sounds
// this.world.b(1028, new BlockPosition(this), 0);
// Paper start
- int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API
+ //int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API // Tuinity - per player view distance
for (EntityPlayer player : ((WorldServer)world).getPlayers()) {
- //final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch
+ final int viewDistance = player.getEffectiveViewDistance(player.getWorldServer().getChunkProvider().playerChunkMap) << 4; // Tuinity - per player view distance
// Paper end
double deltaX = this.locX() - player.locX();
double deltaZ = this.locZ() - player.locZ();
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
index ec2f66ef0cc56229078dacad61cd6ab3a5c6cb11..def793590801b369c12b21de6116fc72028c75b1 100644
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
@@ -114,6 +114,33 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> cachedSingleHashSetTuinity;
// Tuinity end
+ // Tuinity start - view distance api
+ final it.unimi.dsi.fastutil.longs.LongOpenHashSet loadedChunks = new it.unimi.dsi.fastutil.longs.LongOpenHashSet();
+
+ boolean needsChunkCenterUpdate;
+ int viewDistance = -1;
+ public final int getRawViewDistance() {
+ return this.viewDistance;
+ }
+ public final int getEffectiveViewDistance() {
+ return this.getEffectiveViewDistance(((WorldServer)this.world).getChunkProvider().playerChunkMap);
+ }
+ public final int getEffectiveViewDistance(PlayerChunkMap chunkMap) {
+ return this.viewDistance == -1 ? chunkMap.viewDistance : this.viewDistance;
+ }
+
+ int noTickViewDistance = -1;
+ public final int getRawNoTickViewDistance() {
+ return this.noTickViewDistance;
+ }
+ public final int getEffectiveNoTickViewDistance() {
+ return this.getEffectiveNoTickViewDistance(((WorldServer)this.world).getChunkProvider().playerChunkMap);
+ }
+ public final int getEffectiveNoTickViewDistance(PlayerChunkMap chunkMap) {
+ return this.noTickViewDistance == -1 ? chunkMap.getEffectiveNoTickViewDistance() : this.noTickViewDistance;
+ }
+ // Tuinity end - view distance api
+
public EntityPlayer(MinecraftServer minecraftserver, WorldServer worldserver, GameProfile gameprofile, PlayerInteractManager playerinteractmanager) {
super((World) worldserver, gameprofile);
playerinteractmanager.player = this;
diff --git a/src/main/java/net/minecraft/server/EntityWither.java b/src/main/java/net/minecraft/server/EntityWither.java
index 8977c3516b2ee9b970b6274a5c71982b019ac2a6..bace6cf36a9e888278a6c7a5c5866d9f3b9ecf87 100644
--- a/src/main/java/net/minecraft/server/EntityWither.java
+++ b/src/main/java/net/minecraft/server/EntityWither.java
@@ -208,9 +208,9 @@ public class EntityWither extends EntityMonster implements IRangedEntity {
// CraftBukkit start - Use relative location for far away sounds
// this.world.b(1023, new BlockPosition(this), 0);
// Paper start
- int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API
+ //int viewDistance = ((WorldServer) this.world).spigotConfig.viewDistance * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API // Tuinity - per player view distance
for (EntityPlayer player : ((WorldServer)world).getPlayers()) {
- //final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch
+ final int viewDistance = player.getEffectiveViewDistance(player.getWorldServer().getChunkProvider().playerChunkMap) << 4; // Tuinity - per player view distance
// Paper end
double deltaX = this.locX() - player.locX();
double deltaZ = this.locZ() - player.locZ();
diff --git a/src/main/java/net/minecraft/server/HeightMap.java b/src/main/java/net/minecraft/server/HeightMap.java
index 37a6da9ee9aab7acaf2f1559917e415217444747..aa73396100adbf741ed52f457284a78c73d658c3 100644
--- a/src/main/java/net/minecraft/server/HeightMap.java
+++ b/src/main/java/net/minecraft/server/HeightMap.java
@@ -155,7 +155,7 @@ public class HeightMap {
private final String g;
private final HeightMap.Use h;
private final Predicate<IBlockData> i;
- private static final Map<String, HeightMap.Type> j = (Map) SystemUtils.a((Object) Maps.newHashMap(), (hashmap) -> {
+ private static final Map<String, HeightMap.Type> j = SystemUtils.a(Maps.newHashMap(), (hashmap) -> { // Tuinity - decompile fix
HeightMap.Type[] aheightmap_type = values();
int i = aheightmap_type.length;
@@ -167,7 +167,7 @@ public class HeightMap {
});
- private Type(String s, HeightMap.Use heightmap_use, Predicate predicate) {
+ private Type(String s, HeightMap.Use heightmap_use, Predicate<IBlockData> predicate) { // Tuinity - decompile fix
this.g = s;
this.h = heightmap_use;
this.i = predicate;
diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
index a9a2ce3d3f46894c3eb973df239fec088eb1b711..7ad1d23dfb7c9b008f72f62f632c7b613ebd77df 100644
--- a/src/main/java/net/minecraft/server/PlayerChunk.java
+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
@@ -270,6 +270,7 @@ public class PlayerChunk {
return this.entityTickingFuture;
}
+ public final CompletableFuture<Either<Chunk, PlayerChunk.Failure>> getFullChunkFuture() { return this.c(); } // Tuinity - OBFHELPER
public CompletableFuture<Either<Chunk, PlayerChunk.Failure>> c() {
return this.fullChunkFuture;
}
@@ -305,7 +306,7 @@ public class PlayerChunk {
}
public void a(int i, int j, int k) {
- Chunk chunk = this.getChunk();
+ Chunk chunk = this.getFullReadyChunk(); // Tuinity - per player view distance - allow block updates in non-ticking chunks
if (chunk != null) {
this.r |= 1 << (j >> 4);
@@ -325,7 +326,7 @@ public class PlayerChunk {
}
public void a(EnumSkyBlock enumskyblock, int i) {
- Chunk chunk = this.getChunk();
+ Chunk chunk = this.getFullReadyChunk(); // Tuinity - per player view distance - allow block updates in non-ticking chunks
if (chunk != null) {
chunk.setNeedsSaving(true);
@@ -415,9 +416,57 @@ public class PlayerChunk {
}
private void a(Packet<?> packet, boolean flag) {
- this.players.a(this.location, flag).forEach((entityplayer) -> {
- entityplayer.playerConnection.sendPacket(packet);
- });
+ // Tuinity start - per player view distance
+ // there can be potential desync with player's last mapped section and the view distance map, so use the
+ // view distance map here.
+ PlayerChunkMap chunkMap = ((PlayerChunkMap)this.players);
+ com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = chunkMap.playerViewDistanceBroadcastMap;
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> players = viewDistanceMap.getObjectsInRange(this.location);
+ if (players == null) {
+ return;
+ }
+
+ long coordinate = net.minecraft.server.MCUtil.getCoordinateKey(this.location);
+
+ if (flag) { // flag -> border only
+ Object[] backingSet = players.getBackingSet();
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object temp = backingSet[i];
+ if (!(temp instanceof EntityPlayer)) {
+ continue;
+ }
+ EntityPlayer player = (EntityPlayer)temp;
+ if (!player.loadedChunks.contains(coordinate)) {
+ continue;
+ }
+
+ int viewDistance = viewDistanceMap.getLastViewDistance(player);
+ long lastPosition = viewDistanceMap.getLastCoordinate(player);
+
+ int distX = Math.abs(net.minecraft.server.MCUtil.getCoordinateX(lastPosition) - this.location.x);
+ int distZ = Math.abs(net.minecraft.server.MCUtil.getCoordinateZ(lastPosition) - this.location.z);
+
+ if (Math.max(distX, distZ) == viewDistance) {
+ player.playerConnection.sendPacket(packet);
+ }
+ }
+ } else {
+ Object[] backingSet = players.getBackingSet();
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object temp = backingSet[i];
+ if (!(temp instanceof EntityPlayer)) {
+ continue;
+ }
+ EntityPlayer player = (EntityPlayer)temp;
+ if (!player.loadedChunks.contains(coordinate)) {
+ continue;
+ }
+ player.playerConnection.sendPacket(packet);
+ }
+ }
+
+ return;
+ // Tuinity end - per player view distance
}
public CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> a(ChunkStatus chunkstatus, PlayerChunkMap playerchunkmap) {
@@ -618,6 +667,9 @@ public class PlayerChunk {
+ // Tuinity start - per player view distance implementation
+ PlayerChunk.this.chunkMap.getChunkMapDistanceManager().playerTickViewDistanceHandler.onChunkLoad(this.location.x, this.location.z);
+ // Tuinity end - per player view distance implementation
}
});
// Paper end
diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
index 2acdb72d7ee4bba043f8b083a2ed6c2ec1a5111d..16bb6c01c51906c8765158bd2baf568a6957d811 100644
--- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
@@ -80,7 +80,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
private final PlayerMap playerMap;
public final Int2ObjectMap<PlayerChunkMap.EntityTracker> trackedEntities;
private final Queue<Runnable> z;
- int viewDistance; // Paper - private -> package private
+ int viewDistance; public final int getViewDistance() { return this.viewDistance; } // Tuinity - OBFHELPER // Paper - private -> package private
//public final com.destroystokyo.paper.util.PlayerMobDistanceMap playerMobDistanceMap; // Paper // Tuinity - replaced by view distance map
// CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
@@ -111,6 +111,354 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
// Paper start - distance maps
private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<EntityPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
+ // Tuinity start - per player view distance
+ int noTickViewDistance;
+ public final int getRawNoTickViewDistance() {
+ return this.noTickViewDistance;
+ }
+ public final int getEffectiveNoTickViewDistance() {
+ return this.noTickViewDistance == -1 ? this.viewDistance : this.noTickViewDistance;
+ }
+ // we use this map to broadcast chunks to clients
+ // they do not render chunks without having at least neighbours in a 1 chunk radius loaded
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceBroadcastMap;
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceTickMap;
+ public final com.destroystokyo.paper.util.misc.PlayerDistanceTrackingAreaMap playerViewDistanceNoTickMap;
+
+ final ChunkSendThrottler chunkSendThrottler = new ChunkSendThrottler();
+
+ public void updateViewDistance(EntityPlayer player, int viewDistance, int noTickViewDistance) {
+ player.viewDistance = viewDistance;
+ player.noTickViewDistance = noTickViewDistance;
+
+ int chunkX = net.minecraft.server.MCUtil.getChunkCoordinate(player.locX());
+ int chunkZ = net.minecraft.server.MCUtil.getChunkCoordinate(player.locZ());
+
+ int effectiveViewDistance = viewDistance == -1 ? this.viewDistance : viewDistance;
+ int effectiveNoTickViewDistance = Math.max(effectiveViewDistance, noTickViewDistance == -1 ? this.getEffectiveNoTickViewDistance() : noTickViewDistance);
+
+ player.playerConnection.sendPacket(new PacketPlayOutViewDistance(effectiveNoTickViewDistance));
+
+ if (!this.cannotLoadChunks(player)) {
+ this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveViewDistance);
+ this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk neighbours // add an extra one for antixray
+ }
+ this.playerViewDistanceMap.update(player, chunkX, chunkZ, effectiveViewDistance);
+ player.needsChunkCenterUpdate = true;
+ this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need chunk neighbours
+ player.needsChunkCenterUpdate = false;
+ }
+
+ final class ChunkSendThrottler {
+
+ static final int ALREADY_QUEUED = 0;
+ static final int QUEUED = 1;
+ static final int FAILED = 2;
+
+ protected final it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap lastLoadedRadiusByPlayer = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap(512, 0.5f);
+
+ {
+ this.lastLoadedRadiusByPlayer.defaultReturnValue(-1);
+ }
+
+ protected final it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap lastChunkPositionByPlayer = new it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap(512, 0.5f);
+
+ {
+ this.lastChunkPositionByPlayer.defaultReturnValue(Long.MIN_VALUE);
+ }
+
+ protected final it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap chunkSendCountPerPlayer = new it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap(512, 0.5f);
+
+ protected final it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap lastChunkSendStartTimePerPlayer = new it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap(512, 0.5f);
+
+ protected final java.util.List<EntityPlayer> players = new java.util.ArrayList<>(256);
+
+ protected final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<Packet[]> cachedChunkPackets = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>();
+
+ void addPlayer(EntityPlayer player) {
+ this.players.add(player);
+ }
+
+ void removePlayer(EntityPlayer player) {
+ this.players.remove(player);
+ this.lastLoadedRadiusByPlayer.remove(player.getId());
+ this.chunkSendCountPerPlayer.remove(player.getId());
+ this.lastChunkPositionByPlayer.remove(player.getId());
+ player.loadedChunks.clear();
+ }
+
+ int trySendChunk(int chunkX, int chunkZ, EntityPlayer player) {
+ long coordinate = net.minecraft.server.MCUtil.getCoordinateKey(chunkX, chunkZ);
+ PlayerChunk playerChunk = PlayerChunkMap.this.getUpdatingChunk(coordinate);
+
+ if (playerChunk == null) {
+ return FAILED;
+ }
+ Chunk chunk = playerChunk.getFullReadyChunk();
+ if (chunk == null || !chunk.areNeighboursLoaded(1)) {
+ return FAILED;
+ }
+
+ if (!player.loadedChunks.add(coordinate)) {
+ return ALREADY_QUEUED;
+ }
+
+ Packet[] chunkPackets = this.cachedChunkPackets.computeIfAbsent(coordinate, (long keyInMap) -> new Packet[2]);
+ PlayerChunkMap.this.sendChunk(player, chunkPackets, chunk);
+
+ return QUEUED;
+ }
+
+ void tick() {
+ int maxChunkSends = io.akarin.server.Config.maxChunkSendsPerPlayerChoice[MinecraftServer.currentTick % io.akarin.server.Config.maxChunkSendsPerPlayerChoice.length];
+ for (EntityPlayer player : this.players) {
+ int playerId = player.getId();
+ int lastLoadedRadius = this.lastLoadedRadiusByPlayer.get(playerId);
+ long lastChunkPos = this.lastChunkPositionByPlayer.get(playerId);
+ long currentChunkPos = PlayerChunkMap.this.playerViewDistanceBroadcastMap.getLastCoordinate(player);
+
+ if (currentChunkPos == Long.MIN_VALUE) {
+ // not tracking for whatever reason...
+ continue;
+ }
+
+ int newX = net.minecraft.server.MCUtil.getCoordinateX(currentChunkPos);
+ int newZ = net.minecraft.server.MCUtil.getCoordinateZ(currentChunkPos);
+
+ // handle movement
+ if (currentChunkPos != lastChunkPos) {
+ this.lastChunkPositionByPlayer.put(playerId, currentChunkPos);
+ if (lastChunkPos != Long.MIN_VALUE) {
+ int oldX = net.minecraft.server.MCUtil.getCoordinateX(lastChunkPos);
+ int oldZ = net.minecraft.server.MCUtil.getCoordinateZ(lastChunkPos);
+
+ int radiusDiff = Math.max(Math.abs(newX - oldX), Math.abs(newZ - oldZ));
+ lastLoadedRadius = Math.max(-1, lastLoadedRadius - radiusDiff);
+ this.lastLoadedRadiusByPlayer.put(playerId, lastLoadedRadius);
+ }
+ }
+
+ int radius = lastLoadedRadius + 1;
+ int viewDistance = PlayerChunkMap.this.playerViewDistanceBroadcastMap.getLastViewDistance(player);
+
+ if (radius > viewDistance) {
+ // distance map will unload our chunks
+ this.lastLoadedRadiusByPlayer.put(playerId, viewDistance);
+ continue;
+ }
+
+ int totalChunkSends = 0;
+
+ if (totalChunkSends >= maxChunkSends) {
+ continue;
+ }
+
+ radius_loop:
+ for (; radius <= viewDistance; ++radius) {
+ for (int offset = 0; offset <= radius; ++offset) {
+ // try to load the chunks closest to the player by distance
+ // so instead of going left->right on the x axis, we start at the center of the view distance square
+ // and go left and right at the same time
+
+ // try top 2 chunks
+ // top left
+ int attempt = 0;
+ if ((attempt = this.trySendChunk(newX - offset, newZ + radius, player)) == QUEUED) {
+ if (++totalChunkSends >= maxChunkSends) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // top right
+ if ((attempt = this.trySendChunk(newX + offset, newZ + radius, player)) == QUEUED) {
+ if (++totalChunkSends >= maxChunkSends) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // try bottom 2 chunks
+
+ // bottom left
+ if ((attempt = this.trySendChunk(newX - offset, newZ - radius, player)) == QUEUED) {
+ if (++totalChunkSends >= maxChunkSends) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // bottom right
+ if ((attempt = this.trySendChunk(newX + offset, newZ - radius, player)) == QUEUED) {
+ if (++totalChunkSends >= maxChunkSends) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // try left 2 chunks
+
+ // left down
+ if ((attempt = this.trySendChunk(newX - radius, newZ - offset, player)) == QUEUED) {
+ if (++totalChunkSends >= maxChunkSends) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // left up
+ if ((attempt = this.trySendChunk(newX - radius, newZ + offset, player)) == QUEUED) {
+ if (++totalChunkSends >= maxChunkSends) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // try right 2 chunks
+
+ // right down
+ if ((attempt = this.trySendChunk(newX + radius, newZ - offset, player)) == QUEUED) {
+ if (++totalChunkSends >= maxChunkSends) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+
+ // right up
+ if ((attempt = this.trySendChunk(newX + radius, newZ + offset, player)) == QUEUED) {
+ if (++totalChunkSends >= maxChunkSends) {
+ break radius_loop;
+ }
+ } else if (attempt == FAILED) {
+ break radius_loop;
+ }
+ }
+ }
+ int newLoadedRadius = radius - 1;
+ if (newLoadedRadius != lastLoadedRadius) {
+ this.lastLoadedRadiusByPlayer.put(playerId, newLoadedRadius);
+ }
+ }
+ this.cachedChunkPackets.clear();
+ }
+ }
+
+ final class ChunkLoadScheduler implements com.destroystokyo.paper.util.misc.PlayerDistanceTrackingAreaMap.DistanceChangeCallback<EntityPlayer> {
+
+ // higher priroity = lower index
+ // index = distance from player
+ private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet[] queuedLoadsByPriority = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet[65];
+ private final java.util.BitSet nonEmptyQueues = new java.util.BitSet(this.queuedLoadsByPriority.length);
+
+ private final it.unimi.dsi.fastutil.longs.Long2LongLinkedOpenHashMap pendingLoads = new it.unimi.dsi.fastutil.longs.Long2LongLinkedOpenHashMap(64, 0.7f);
+ {
+ this.pendingLoads.defaultReturnValue(-1L);
+ }
+
+ private final it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet loadedChunks = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(8192, 0.7f);
+
+ private long chunkLoadCounter;
+
+ {
+ for (int i = 0, len = this.queuedLoadsByPriority.length; i < len; ++i) {
+ this.queuedLoadsByPriority[i] = new it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet(32, 0.7f);
+ }
+ }
+
+ private final int maxPendingLoads;
+
+ ChunkLoadScheduler(int maxPendingLoads) {
+ this.maxPendingLoads = maxPendingLoads;
+ }
+
+ void queueMaxLoads() {
+ while (this.pendingLoads.size() < this.maxPendingLoads) {
+ int firstQueue = this.nonEmptyQueues.nextSetBit(0);
+ if (firstQueue == -1) {
+ // nothing left
+ return;
+ }
+
+ long coordinate = this.queuedLoadsByPriority[firstQueue].removeFirstLong();
+ if (this.queuedLoadsByPriority[firstQueue].isEmpty()) {
+ this.nonEmptyQueues.clear(firstQueue);
+ }
+ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(coordinate);
+ PlayerChunkMap.this.world.getChunkProvider().addTicketAtLevel(TicketType.PLAYER, chunkPos, 33, chunkPos);
+
+ // now hook into the load
+
+ PlayerChunkMap.this.world.getChunkProvider().tickDistanceManager(); // bring the chunkholder up to 33
+ PlayerChunk chunkHolder = PlayerChunkMap.this.getUpdatingChunk(coordinate);
+
+ CompletableFuture<Either<Chunk, PlayerChunk.Failure>> loadFuture = chunkHolder.getFullChunkFuture();
+
+ final long loadCounterExpected = this.chunkLoadCounter++;
+
+ this.pendingLoads.put(coordinate, loadCounterExpected);
+
+ loadFuture.thenAcceptAsync((either) -> {
+ // no matter what, try to load more chunks
+
+ if (!ChunkLoadScheduler.this.pendingLoads.remove(coordinate, loadCounterExpected)) {
+ // something cancelled us (or we multi loaded)
+ ChunkLoadScheduler.this.queueMaxLoads();
+ return;
+ }
+ ChunkLoadScheduler.this.loadedChunks.add(coordinate);
+ ChunkLoadScheduler.this.queueMaxLoads();
+ }, PlayerChunkMap.this.world.getChunkProvider().serverThreadQueue).exceptionally((throwable) -> {
+ MinecraftServer.LOGGER.fatal("Failed to handle load future for player chunk load", throwable);
+ return null;
+ });
+ }
+ }
+
+ @Override
+ public void accept(int posX, int posZ, int oldNearestDistance, int newNearestDistance, com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> state) {
+ long coordinate = net.minecraft.server.MCUtil.getCoordinateKey(posX, posZ);
+
+ if (newNearestDistance == -1 && ((this.loadedChunks.remove(coordinate)) || (this.pendingLoads.remove(coordinate) != -1L))) {
+ // load needs to be dropped.
+ ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(posX, posZ);
+ PlayerChunkMap.this.world.getChunkProvider().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 33, chunkPos);
+ return;
+ }
+
+ if (this.loadedChunks.contains(coordinate) || this.pendingLoads.containsKey(coordinate)) {
+ // no need to queue - we're already loaded/loading
+ return;
+ }
+
+ if (oldNearestDistance != -1) {
+ // remove from old
+ it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet queue = this.queuedLoadsByPriority[oldNearestDistance];
+ if (queue.remove(coordinate) && queue.isEmpty()) {
+ this.nonEmptyQueues.clear(oldNearestDistance);
+ }
+
+ if (newNearestDistance == -1) {
+ // we're done here - removed from the queue, and there is no pending load
+ return;
+ }
+ }
+
+ // queue the load
+ this.queuedLoadsByPriority[newNearestDistance].add(coordinate);
+ this.nonEmptyQueues.set(newNearestDistance);
+ }
+ }
+ final ChunkLoadScheduler chunkLoadScheduler;
+
+ // Tuinity end - per player view distance
+
void addPlayerToDistanceMaps(EntityPlayer player) {
this.updateMaps(player);
@@ -142,10 +490,22 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
void addPlayerToDistanceMapsTuinity(EntityPlayer player) {
this.updateMapsTuinity(player);
+
+ // Tuinity start - per player view distance
+ this.getChunkMapDistanceManager().playerTickViewDistanceHandler.addPlayer(player);
+ this.chunkSendThrottler.addPlayer(player);
+ // Tuinity end - per player view distance
}
void removePlayerFromDistanceMapsTuinity(EntityPlayer player) {
this.playerViewDistanceMap.remove(player);
+ // Tuinity start - per player view distance
+ this.playerViewDistanceBroadcastMap.remove(player);
+ this.playerViewDistanceTickMap.remove(player);
+ this.playerViewDistanceNoTickMap.remove(player);
+ this.getChunkMapDistanceManager().playerTickViewDistanceHandler.removePlayer(player);
+ this.chunkSendThrottler.removePlayer(player);
+ // Tuinity end - per player view distance
}
void updateDistanceMapsTuinity(EntityPlayer player) {
@@ -156,7 +516,20 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
int chunkX = net.minecraft.server.MCUtil.getChunkCoordinate(player.locX());
int chunkZ = net.minecraft.server.MCUtil.getChunkCoordinate(player.locZ());
- this.playerViewDistanceMap.update(player, chunkX, chunkZ, this.viewDistance);
+ this.playerViewDistanceMap.update(player, chunkX, chunkZ, player.getEffectiveViewDistance(this)); // Tuinity - per player view distance
+
+ // Tuinity start - per player view distance
+ int effectiveViewDistance = player.getEffectiveViewDistance(this);
+ int effectiveNoTickViewDistance = Math.max(effectiveViewDistance, player.getEffectiveNoTickViewDistance(this));
+
+ if (!this.cannotLoadChunks(player)) {
+ this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveViewDistance);
+ this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk neighbours // add an extra one for antixray
+ }
+ player.needsChunkCenterUpdate = true;
+ this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need chunk neighbours
+ player.needsChunkCenterUpdate = false;
+ // Tuinity end - per player view distance
}
// Tuinity end
@@ -230,6 +603,37 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
com.destroystokyo.paper.util.misc.PooledLinkedHashSets<EntityPlayer> sets = this.pooledEntityPlayerSets;
this.playerViewDistanceMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(sets);
// Tuinity end - distance maps
+ // Tuinity start - per player view distance
+ this.chunkLoadScheduler = new ChunkLoadScheduler(this.world.akarinConfig.maxPendingChunkLoads);
+ this.setNoTickViewDistance(this.world.akarinConfig.noTickViewDistance);
+ this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(sets,
+ null,
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
+ if (newState != null) {
+ return;
+ }
+ PlayerChunkMap.this.chunkDistanceManager.playerTickViewDistanceHandler.playerMoveOutOfRange(rangeX, rangeZ);
+ });
+ this.chunkDistanceManager.playerTickViewDistanceHandler.areaMap = this.playerViewDistanceTickMap;
+ this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerDistanceTrackingAreaMap(sets,
+ null,
+ null,
+ this.chunkLoadScheduler);
+ this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(sets,
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
+ if (player.needsChunkCenterUpdate) {
+ player.needsChunkCenterUpdate = false;
+ player.playerConnection.sendPacket(new PacketPlayOutViewCentre(currPosX, currPosZ));
+ }
+ },
+ (EntityPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> newState) -> {
+ PlayerChunkMap.this.sendChunk(player, new ChunkCoordIntPair(rangeX, rangeZ), null, true, false); // unloaded, loaded
+ player.loadedChunks.remove(net.minecraft.server.MCUtil.getCoordinateKey(rangeX, rangeZ));
+ });
+ // Tuinity end - per player view distance
}
public void updatePlayerMobTypeMap(Entity entity) {
@@ -1053,11 +1457,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
completablefuture1.thenAcceptAsync((either) -> {
either.mapLeft((chunk) -> {
this.u.getAndIncrement();
- Packet<?>[] apacket = new Packet[2];
-
- this.a(chunkcoordintpair, false).forEach((entityplayer) -> {
- this.a(entityplayer, apacket, chunk);
- });
+ // Tuinity - per player view distance - moved to full chunk load, instead of ticking load
return Either.left(chunk);
});
}, (runnable) -> {
@@ -1161,32 +1561,39 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
} // Paper
}
- protected void setViewDistance(int i) {
- int j = MathHelper.clamp(i + 1, 3, 33);
+ public void setViewDistance(int i) { // Tuinity - make public
+ int j = MathHelper.clamp(i + 1, 3, 33) - 1; // Tuinity - we correctly handle view distance, no need to add 1
if (j != this.viewDistance) {
int k = this.viewDistance;
this.viewDistance = j;
- this.chunkDistanceManager.a(this.viewDistance);
- ObjectIterator objectiterator = this.updatingChunks.values().iterator();
-
- while (objectiterator.hasNext()) {
- PlayerChunk playerchunk = (PlayerChunk) objectiterator.next();
- ChunkCoordIntPair chunkcoordintpair = playerchunk.i();
- Packet<?>[] apacket = new Packet[2];
+ // Tuinity start - view distance api
+ this.chunkDistanceManager.setGlobalViewDistance(this.viewDistance, this);
+ if (this.world != null && this.world.players != null) { // ... called inside constructor, where these may not be initialized
+ for (EntityPlayer player : this.world.players) {
+ this.updateViewDistance(player, player.getRawViewDistance(), player.getRawNoTickViewDistance());
+ }
+ }
+ // Tuinity end - view distance api
+ }
- this.a(chunkcoordintpair, false).forEach((entityplayer) -> {
- int l = b(chunkcoordintpair, entityplayer, true);
- boolean flag = l <= k;
- boolean flag1 = l <= this.viewDistance;
+ }
- this.sendChunk(entityplayer, chunkcoordintpair, apacket, flag, flag1);
- });
+ // Tuinity start - no tick view distance
+ public void setNoTickViewDistance(int noTickViewDistance) {
+ // modeled after the above
+ noTickViewDistance = noTickViewDistance < 0 ? -1 : MathHelper.clamp(noTickViewDistance, 2, 32);
+ if (this.noTickViewDistance != noTickViewDistance) {
+ this.noTickViewDistance = noTickViewDistance;
+ if (this.world != null && this.world.players != null) { // ... called inside constructor, where these may not be initialized
+ for (EntityPlayer player : this.world.players) {
+ this.updateViewDistance(player, player.getRawViewDistance(), player.getRawNoTickViewDistance());
+ }
}
}
-
}
+ // Tuinity end - no tick view distance
protected void sendChunk(EntityPlayer entityplayer, ChunkCoordIntPair chunkcoordintpair, Packet<?>[] apacket, boolean flag, boolean flag1) {
if (entityplayer.world == this.world) {
@@ -1194,7 +1601,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
PlayerChunk playerchunk = this.getVisibleChunk(chunkcoordintpair.pair());
if (playerchunk != null) {
- Chunk chunk = playerchunk.getChunk();
+ Chunk chunk = playerchunk.getFullReadyChunk(); // Tuinity - per player view distance
if (chunk != null) {
this.a(entityplayer, apacket, chunk);
@@ -1440,6 +1847,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
});
}
+ private boolean cannotLoadChunks(EntityPlayer entityplayer) { return this.b(entityplayer); } // Tuinity - OBFHELPER
private boolean b(EntityPlayer entityplayer) {
return entityplayer.isSpectator() && !this.world.getGameRules().getBoolean(GameRules.SPECTATORS_GENERATE_CHUNKS);
}
@@ -1465,13 +1873,11 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
}
}
- for (int k = i - this.viewDistance; k <= i + this.viewDistance; ++k) {
- for (int l = j - this.viewDistance; l <= j + this.viewDistance; ++l) {
- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(k, l);
-
- this.sendChunk(entityplayer, chunkcoordintpair, new Packet[2], !flag, flag);
- }
+ // Tuinity start - view distance map handles this
+ if (flag) {
+ this.updateDistanceMapsTuinity(entityplayer);
}
+ // Tuinity end - view distance map handles this
}
@@ -1479,7 +1885,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
SectionPosition sectionposition = SectionPosition.a((Entity) entityplayer);
entityplayer.a(sectionposition);
- entityplayer.playerConnection.sendPacket(new PacketPlayOutViewCentre(sectionposition.a(), sectionposition.c()));
+ //entityplayer.playerConnection.sendPacket(new PacketPlayOutViewCentre(sectionposition.a(), sectionposition.c())); // Tuinity - distance map handles this now
return sectionposition;
}
@@ -1546,56 +1952,54 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
int k1;
int l1;
- if (Math.abs(i1 - i) <= this.viewDistance * 2 && Math.abs(j1 - j) <= this.viewDistance * 2) {
- k1 = Math.min(i, i1) - this.viewDistance;
- l1 = Math.min(j, j1) - this.viewDistance;
- int i2 = Math.max(i, i1) + this.viewDistance;
- int j2 = Math.max(j, j1) + this.viewDistance;
+ // Tuinity - handled by distance map
+ this.updateMaps(entityplayer); // Paper - distance maps
+ this.updateDistanceMapsTuinity(entityplayer); // Tuinity - distance maps
+ }
- for (int k2 = k1; k2 <= i2; ++k2) {
- for (int l2 = l1; l2 <= j2; ++l2) {
- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(k2, l2);
- boolean flag3 = a(chunkcoordintpair, i1, j1) <= this.viewDistance;
- boolean flag4 = a(chunkcoordintpair, i, j) <= this.viewDistance;
+ @Override
+ public Stream<EntityPlayer> a(ChunkCoordIntPair chunkcoordintpair, boolean flag) {
+ // Tuinity start - per player view distance
+ // there can be potential desync with player's last mapped section and the view distance map, so use the
+ // view distance map here.
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> inRange = this.playerViewDistanceBroadcastMap.getObjectsInRange(chunkcoordintpair);
+
+ if (inRange == null) {
+ return Stream.empty();
+ }
+ // all current cases are inlined so we wont hit this code, it's just in case plugins or future updates use it
+ List<EntityPlayer> players = new ArrayList<>();
+ Object[] backingSet = inRange.getBackingSet();
+
+ if (flag) { // flag -> border only
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object temp = backingSet[i];
+ if (!(temp instanceof EntityPlayer)) {
+ continue;
+ }
+ EntityPlayer player = (EntityPlayer)temp;
+ int viewDistance = this.playerViewDistanceBroadcastMap.getLastViewDistance(player);
+ long lastPosition = this.playerViewDistanceBroadcastMap.getLastCoordinate(player);
- this.sendChunk(entityplayer, chunkcoordintpair, new Packet[2], flag3, flag4);
+ int distX = Math.abs(net.minecraft.server.MCUtil.getCoordinateX(lastPosition) - chunkcoordintpair.x);
+ int distZ = Math.abs(net.minecraft.server.MCUtil.getCoordinateZ(lastPosition) - chunkcoordintpair.z);
+
+ if (Math.max(distX, distZ) == viewDistance) {
+ players.add(player);
}
}
} else {
- ChunkCoordIntPair chunkcoordintpair1;
- boolean flag5;
- boolean flag6;
-
- for (k1 = i1 - this.viewDistance; k1 <= i1 + this.viewDistance; ++k1) {
- for (l1 = j1 - this.viewDistance; l1 <= j1 + this.viewDistance; ++l1) {
- chunkcoordintpair1 = new ChunkCoordIntPair(k1, l1);
- flag5 = true;
- flag6 = false;
- this.sendChunk(entityplayer, chunkcoordintpair1, new Packet[2], true, false);
- }
- }
-
- for (k1 = i - this.viewDistance; k1 <= i + this.viewDistance; ++k1) {
- for (l1 = j - this.viewDistance; l1 <= j + this.viewDistance; ++l1) {
- chunkcoordintpair1 = new ChunkCoordIntPair(k1, l1);
- flag5 = false;
- flag6 = true;
- this.sendChunk(entityplayer, chunkcoordintpair1, new Packet[2], false, true);
+ for (int i = 0, len = backingSet.length; i < len; ++i) {
+ Object temp = backingSet[i];
+ if (!(temp instanceof EntityPlayer)) {
+ continue;
}
+ EntityPlayer player = (EntityPlayer)temp;
+ players.add(player);
}
}
- this.updateMaps(entityplayer); // Paper - distance maps
- this.updateDistanceMapsTuinity(entityplayer); // Tuinity - distance maps
- }
-
- @Override
- public Stream<EntityPlayer> a(ChunkCoordIntPair chunkcoordintpair, boolean flag) {
- return this.playerMap.a(chunkcoordintpair.pair()).filter((entityplayer) -> {
- int i = b(chunkcoordintpair, entityplayer, true);
-
- return i > this.viewDistance ? false : !flag || i == this.viewDistance;
- });
+ return players.stream();
}
protected void addEntity(Entity entity) {
@@ -1733,6 +2137,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
}
+ final void sendChunk(EntityPlayer entityplayer, Packet<?>[] apacket, Chunk chunk) { this.a(entityplayer, apacket, chunk); } // Tuinity - OBFHELPER
private void a(EntityPlayer entityplayer, Packet<?>[] apacket, Chunk chunk) {
if (apacket[0] == null) {
apacket[0] = new PacketPlayOutMapChunk(chunk, 65535, true); // Paper - Anti-Xray
@@ -1866,7 +2271,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
org.spigotmc.AsyncCatcher.catchOp("player tracker update"); // Spigot
if (entityplayer != this.tracker) {
Vec3D vec3d = entityplayer.getPositionVector().d(this.tracker.getPositionVector()); // MC-155077, SPIGOT-5113
- int i = Math.min(this.b(), (PlayerChunkMap.this.viewDistance - 1) * 16);
+ int i = Math.min(this.b(), (entityplayer.getEffectiveViewDistance(PlayerChunkMap.this)) * 16); // Tuinity - per player view distance
boolean flag = vec3d.x >= (double) (-i) && vec3d.x <= (double) i && vec3d.z >= (double) (-i) && vec3d.z <= (double) i && this.tracker.a(entityplayer);
if (flag) {
@@ -1877,7 +2282,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
PlayerChunk playerchunk = PlayerChunkMap.this.getVisibleChunk(chunkcoordintpair.pair());
if (playerchunk != null && playerchunk.getChunk() != null) {
- flag1 = PlayerChunkMap.b(chunkcoordintpair, entityplayer, false) <= PlayerChunkMap.this.viewDistance;
+ flag1 = PlayerChunkMap.b(chunkcoordintpair, entityplayer, false) <= (1 + PlayerChunkMap.this.playerViewDistanceTickMap.getLastViewDistance(entityplayer)); // Tuinity - per player view distance
}
}
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
index 9ca3fdc9cf5df3651d9b9267806c9f4b566515cb..723f781b2e4d015703b0e0488893352fdcb17e86 100644
--- a/src/main/java/net/minecraft/server/PlayerList.java
+++ b/src/main/java/net/minecraft/server/PlayerList.java
@@ -157,7 +157,7 @@ public abstract class PlayerList {
// CraftBukkit - getType()
// Spigot - view distance
- playerconnection.sendPacket(new PacketPlayOutLogin(entityplayer.getId(), entityplayer.playerInteractManager.getGameMode(), WorldData.c(worlddata.getSeed()), worlddata.isHardcore(), worldserver.worldProvider.getDimensionManager().getType(), this.getMaxPlayers(), worlddata.getType(), worldserver.spigotConfig.viewDistance, flag1, !flag));
+ playerconnection.sendPacket(new PacketPlayOutLogin(entityplayer.getId(), entityplayer.playerInteractManager.getGameMode(), WorldData.c(worlddata.getSeed()), worlddata.isHardcore(), worldserver.worldProvider.getDimensionManager().getType(), this.getMaxPlayers(), worlddata.getType(), Math.max(entityplayer.getEffectiveViewDistance(worldserver.getChunkProvider().playerChunkMap), entityplayer.getEffectiveNoTickViewDistance(worldserver.getChunkProvider().playerChunkMap)), flag1, !flag)); // Tuinity - per player view distance
entityplayer.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
// Akarin start - send multiple packets at once
/*
@@ -714,7 +714,7 @@ public abstract class PlayerList {
WorldData worlddata = worldserver.getWorldData();
entityplayer1.playerConnection.sendPacket(new PacketPlayOutRespawn(worldserver.worldProvider.getDimensionManager().getType(), WorldData.c(worldserver.getWorldData().getSeed()), worldserver.getWorldData().getType(), entityplayer1.playerInteractManager.getGameMode()));
- entityplayer1.playerConnection.sendPacket(new PacketPlayOutViewDistance(worldserver.spigotConfig.viewDistance)); // Spigot
+ entityplayer1.playerConnection.sendPacket(new PacketPlayOutViewDistance(Math.max(entityplayer1.getEffectiveViewDistance(worldserver.getChunkProvider().playerChunkMap), entityplayer1.getEffectiveNoTickViewDistance(worldserver.getChunkProvider().playerChunkMap)))); // Spigot // Tuinity - per player view distance
entityplayer1.spawnIn(worldserver);
entityplayer1.dead = false;
entityplayer1.playerConnection.teleport(new Location(worldserver.getWorld(), entityplayer1.locX(), entityplayer1.locY(), entityplayer1.locZ(), entityplayer1.yaw, entityplayer1.pitch));
@@ -1198,7 +1198,7 @@ public abstract class PlayerList {
public void a(int i) {
this.viewDistance = i;
- this.sendAll(new PacketPlayOutViewDistance(i));
+ //this.sendAll(new PacketPlayOutViewDistance(i)); // Tuinity - move into setViewDistance
Iterator iterator = this.server.getWorlds().iterator();
while (iterator.hasNext()) {
diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
index 7129cb0260316e166b4b15cded0e0c0b32433b7f..804efba68124cc4e3b7acb071b3c369c682ec4ec 100644
--- a/src/main/java/net/minecraft/server/World.java
+++ b/src/main/java/net/minecraft/server/World.java
@@ -440,8 +440,13 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
this.b(blockposition, iblockdata1, iblockdata2);
}
- if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getState() != null && chunk.getState().isAtLeast(PlayerChunk.State.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement
+ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getState() != null && chunk.getState().isAtLeast(PlayerChunk.State.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement // Tuinity - diff on change, see below
this.notify(blockposition, iblockdata1, iblockdata, i);
+ // Tuinity start - per player view distance - allow block updates for non-ticking chunks in player view distance
+ // if copied from above
+ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((WorldServer)this).getChunkProvider().playerChunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(net.minecraft.server.MCUtil.getCoordinateKey(blockposition)) != null)) {
+ ((WorldServer)this).getChunkProvider().flagDirty(blockposition);
+ // Tuinity end - per player view distance
}
if (!this.isClientSide && (i & 1) != 0) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
index 0f3199ba4a228cc60db3abfa4ac926b92cbb4b4b..d2b9696ca4382e156125f96805d60f1285fca8bb 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
@@ -2486,10 +2486,43 @@ public class CraftWorld implements World {
// Spigot start
@Override
public int getViewDistance() {
- return world.spigotConfig.viewDistance;
+ return getHandle().getChunkProvider().playerChunkMap.getViewDistance(); // Tuinity start - per player view distance
}
// Spigot end
+ // Tuinity start - per player view distance
+
+
+ @Override
+ public void setViewDistance(int viewDistance) {
+ org.spigotmc.AsyncCatcher.catchOp("Cannot update view distance safely off of the main thread");
+ if (viewDistance < 2 || viewDistance > 32) {
+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
+ }
+ net.minecraft.server.PlayerChunkMap chunkMap = getHandle().getChunkProvider().playerChunkMap;
+ if (viewDistance != chunkMap.getViewDistance()) {
+ chunkMap.setViewDistance(viewDistance);
+ }
+ }
+
+ @Override
+ public int getNoTickViewDistance() {
+ return getHandle().getChunkProvider().playerChunkMap.getEffectiveNoTickViewDistance();
+ }
+
+ @Override
+ public void setNoTickViewDistance(int viewDistance) {
+ org.spigotmc.AsyncCatcher.catchOp("Cannot update view distance safely off of the main thread");
+ if (viewDistance < 2 || viewDistance > 32) {
+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
+ }
+ net.minecraft.server.PlayerChunkMap chunkMap = getHandle().getChunkProvider().playerChunkMap;
+ if (viewDistance != chunkMap.getRawNoTickViewDistance()) {
+ chunkMap.setNoTickViewDistance(viewDistance);
+ }
+ }
+ // Tuinity end - per player view distance
+
// Spigot start
private final Spigot spigot = new Spigot()
{
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index b76379a17ca4c1ad009acfd4407d103ecb476a4e..b767da678e6392e7411c940916417192aa458f2a 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -1966,13 +1966,39 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
@Override
public int getViewDistance() {
- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement"); // TODO
+ return getHandle().getEffectiveViewDistance(); // Tuinity - per player view distance
}
@Override
public void setViewDistance(int viewDistance) {
- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement"); // TODO
+ // Tuinity start - per player view distance
+ org.spigotmc.AsyncCatcher.catchOp("Cannot update view distance safely off of the main thread");
+ if ((viewDistance < 2 || viewDistance > 32) && viewDistance != -1) {
+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
+ }
+ if (viewDistance != getHandle().getRawViewDistance()) {
+ ((WorldServer)getHandle().world).getChunkProvider().playerChunkMap.updateViewDistance(getHandle(), viewDistance, getHandle().getRawNoTickViewDistance());
+ }
+ // Tuinity end - per player view distance
+ }
+
+ // Tuinity start - per player view distance
+ @Override
+ public int getNoTickViewDistance() {
+ return getHandle().getEffectiveNoTickViewDistance();
+ }
+
+ @Override
+ public void setNoTickViewDistance(int viewDistance) {
+ org.spigotmc.AsyncCatcher.catchOp("Cannot update view distance safely off of the main thread");
+ if ((viewDistance < 2 || viewDistance > 32) && viewDistance != -1) {
+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
+ }
+ if (viewDistance != getHandle().getRawNoTickViewDistance()) {
+ ((WorldServer)getHandle().world).getChunkProvider().playerChunkMap.updateViewDistance(getHandle(), getHandle().getRawViewDistance(), viewDistance);
+ }
}
+ // Tuinity end - per player view distance
@Override
public <T> T getClientOption(ClientOption<T> type) {
diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
index 795817177aae00a5157ae77b2192841c8d028732..f6e8f7d9c64bf8f34b13636e4d0e13746fd2c4f4 100644
--- a/src/main/java/org/spigotmc/ActivationRange.java
+++ b/src/main/java/org/spigotmc/ActivationRange.java
@@ -46,6 +46,7 @@ import net.minecraft.server.EntityInsentient;
import net.minecraft.server.EntityLlama;
import net.minecraft.server.EntityWaterAnimal;
// Paper end
+import net.minecraft.server.WorldServer; // Tuinity
public class ActivationRange
{
@@ -137,14 +138,15 @@ public class ActivationRange
final int waterActivationRange = world.spigotConfig.waterActivationRange; // Paper
final ChunkProviderServer chunkProvider = (ChunkProviderServer) world.getChunkProvider(); // Paper
- int maxRange = Math.max( monsterActivationRange, animalActivationRange );
- maxRange = Math.max( maxRange, raiderActivationRange );
- maxRange = Math.max( maxRange, miscActivationRange );
- maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange );
+ // Tuinity start - per player view distance
+ int maxRangeTemp = Math.max( monsterActivationRange, animalActivationRange );
+ maxRangeTemp = Math.max( maxRangeTemp, raiderActivationRange );
+ maxRangeTemp = Math.max( maxRangeTemp, miscActivationRange );
for ( EntityHuman player : world.getPlayers() )
{
-
+ final int maxRange = Math.min( ( ( player instanceof net.minecraft.server.EntityPlayer ? ((net.minecraft.server.EntityPlayer)player).getEffectiveViewDistance(((WorldServer)world).getChunkProvider().playerChunkMap) : world.spigotConfig.viewDistance ) << 4 ) - 8, maxRangeTemp );
+ // Tuinity end - per player view distance
player.activatedTick = MinecraftServer.currentTick;
maxBB = player.getBoundingBox().grow( maxRange, 256, maxRange );
ActivationType.MISC.boundingBox = player.getBoundingBox().grow( miscActivationRange, 256, miscActivationRange );