1500 lines
78 KiB
Diff
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 );
|