package net.minecraft.server;
import co.aikar.timings.Timing;
import com.google.common.base.Predicate;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.koloboke.collect.set.hash.HashObjSets;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
// CraftBukkit start
import java.util.LinkedList;
// CraftBukkit end
public class PlayerChunkMap {
private static final Predicate<EntityPlayer> a = new Predicate() {
public boolean a(@Nullable EntityPlayer entityplayer) {
return entityplayer != null && !entityplayer.isSpectator();
}
@Override
public boolean apply(@Nullable Object object) {
return this.a((EntityPlayer) object);
}
};
private static final Predicate<EntityPlayer> b = new Predicate() {
public boolean a(@Nullable EntityPlayer entityplayer) {
return entityplayer != null && (!entityplayer.isSpectator() || entityplayer.x().getGameRules().getBoolean("spectatorsGenerateChunks"));
}
@Override
public boolean apply(@Nullable Object object) {
return this.a((EntityPlayer) object);
}
};
private final WorldServer world;
private final List<EntityPlayer> managedPlayers = Lists.newArrayList();
private final Long2ObjectMap<PlayerChunk> e = new Long2ObjectOpenHashMap(4096);
private final Set<PlayerChunk> f = HashObjSets.newMutableSet();
private final List<PlayerChunk> g = Lists.newLinkedList();
private final List<PlayerChunk> h = Lists.newLinkedList();
private final List<PlayerChunk> i = Lists.newArrayList();
private int j;public int getViewDistance() { return j; } // Paper OBFHELPER
private long k;
private boolean l = true;
private boolean m = true;
private boolean wasNotEmpty; // CraftBukkit - add field
public PlayerChunkMap(WorldServer worldserver, int viewDistance /* Spigot */) {
this.world = worldserver;
this.a(worldserver.spigotConfig.viewDistance); // Spigot
}
public WorldServer getWorld() {
return this.world;
}
public Iterator<Chunk> b() {
final Iterator iterator = this.i.iterator();
return new AbstractIterator() {
protected Chunk a() {
while (true) {
if (iterator.hasNext()) {
PlayerChunk playerchunk = (PlayerChunk) iterator.next();
Chunk chunk = playerchunk.f();
if (chunk == null) {
continue;
}
if (!chunk.v() && chunk.isDone()) {
return chunk;
}
if (!chunk.j()) {
return chunk;
}
if (!playerchunk.a(128.0D, PlayerChunkMap.a)) {
continue;
}
return chunk;
}
return (Chunk) this.endOfData();
}
}
@Override
protected Object computeNext() {
return this.a();
}
};
}
public void flush() {
long i = this.world.getTime();
int j;
PlayerChunk playerchunk;
if (i - this.k > 8000L) {
try (Timing ignored = world.timings.doChunkMapUpdate.startTiming()) { // Paper
this.k = i;
for (j = 0; j < this.i.size(); ++j) {
playerchunk = this.i.get(j);
playerchunk.d();
playerchunk.c();
}
} // Paper timing
}
if (!this.f.isEmpty()) {
try (Timing ignored = world.timings.doChunkMapToUpdate.startTiming()) { // Paper
Iterator iterator = this.f.iterator();
while (iterator.hasNext()) {
playerchunk = (PlayerChunk) iterator.next();
playerchunk.d();
}
this.f.clear();
} // Paper timing
}
if (this.l && i % 4L == 0L) {
this.l = false;
try (Timing ignored = world.timings.doChunkMapSortMissing.startTiming()) { // Paper
Collections.sort(this.h, new Comparator() {
public int a(PlayerChunk playerchunk, PlayerChunk playerchunk1) {
return ComparisonChain.start().compare(playerchunk.g(), playerchunk1.g()).result();
}
@Override
public int compare(Object object, Object object1) {
return this.a((PlayerChunk) object, (PlayerChunk) object1);
}
});
} // Paper timing
}
if (this.m && i % 4L == 2L) {
this.m = false;
try (Timing ignored = world.timings.doChunkMapSortSendToPlayers.startTiming()) { // Paper
Collections.sort(this.g, new Comparator() {
public int a(PlayerChunk playerchunk, PlayerChunk playerchunk1) {
return ComparisonChain.start().compare(playerchunk.g(), playerchunk1.g()).result();
}
@Override
public int compare(Object object, Object object1) {
return this.a((PlayerChunk) object, (PlayerChunk) object1);
}
});
} // Paper timing
}
if (!this.h.isEmpty()) {
try (Timing ignored = world.timings.doChunkMapPlayersNeedingChunks.startTiming()) { // Paper
// Spigot start
org.spigotmc.SlackActivityAccountant activityAccountant = this.world.getMinecraftServer().slackActivityAccountant;
activityAccountant.startActivity(0.5);
// Spigot end
Iterator iterator1 = this.h.iterator();
while (iterator1.hasNext()) {
PlayerChunk playerchunk1 = (PlayerChunk) iterator1.next();
if (playerchunk1.f() == null) {
boolean flag = playerchunk1.a(PlayerChunkMap.b);
if (playerchunk1.a(flag)) {
iterator1.remove();
if (playerchunk1.b()) {
this.g.remove(playerchunk1);
}
if (activityAccountant.activityTimeIsExhausted()) { // Spigot
break;
}
}
// CraftBukkit start - SPIGOT-2891: remove once chunk has been provided
} else {
iterator1.remove();
}
// CraftBukkit end
}
activityAccountant.endActivity(); // Spigot
} // Paper timing
}
if (!this.g.isEmpty()) {
j = 81;
try (Timing ignored = world.timings.doChunkMapPendingSendToPlayers.startTiming()) { // Paper
Iterator iterator2 = this.g.iterator();
while (iterator2.hasNext()) {
PlayerChunk playerchunk2 = (PlayerChunk) iterator2.next();
if (playerchunk2.b()) {
iterator2.remove();
--j;
if (j < 0) {
break;
}
}
}
} // Paper timing
}
if (this.managedPlayers.isEmpty()) {
try (Timing ignored = world.timings.doChunkMapUnloadChunks.startTiming()) { // Paper
WorldProvider worldprovider = this.world.worldProvider;
if (!worldprovider.e()) {
this.world.getChunkProviderServer().b();
}
} // Paper timing
}
}
public boolean a(int i, int j) {
long k = chunkIndex(i, j);
return this.e.get(k) != null;
}
@Nullable
public PlayerChunk getChunk(int i, int j) {
return this.e.get(chunkIndex(i, j));
}
private PlayerChunk c(int i, int j) {
long k = chunkIndex(i, j);
PlayerChunk playerchunk = this.e.get(k);
if (playerchunk == null) {
playerchunk = new PlayerChunk(this, i, j);
this.e.put(k, playerchunk);
this.i.add(playerchunk);
if (playerchunk.f() == null) {
this.h.add(playerchunk);
}
if (!playerchunk.b()) {
this.g.add(playerchunk);
}
}
return playerchunk;
}
// CraftBukkit start - add method
public final boolean isChunkInUse(int x, int z) {
PlayerChunk pi = getChunk(x, z);
if (pi != null) {
return (pi.c.size() > 0);
}
return false;
}
// CraftBukkit end
/**
* Mark a block for update
*/
public void flagDirty(BlockPosition position) {
int cx = position.getX() >> 4;
int cz = position.getZ() >> 4;
PlayerChunk playerchunk = this.getChunk(cx, cz);
if (playerchunk != null) playerchunk.blockChanged(position.getX() & 15, position.getY(), position.getZ() & 15);
}
public void addPlayer(EntityPlayer entityplayer) {
int i = (int) entityplayer.locX >> 4;
int j = (int) entityplayer.locZ >> 4;
entityplayer.d = entityplayer.locX;
entityplayer.e = entityplayer.locZ;
// CraftBukkit start - Load nearby chunks first
List<ChunkCoordIntPair> chunkList = new LinkedList<ChunkCoordIntPair>();
// Paper start - Player view distance API
int viewDistance = entityplayer.getViewDistance();
for (int k = i - viewDistance; k <= i + viewDistance; ++k) {
for (int l = j - viewDistance; l <= j + viewDistance; ++l) {
// Paper end
chunkList.add(new ChunkCoordIntPair(k, l));
}
}
Collections.sort(chunkList, new ChunkCoordComparator(entityplayer));
for (ChunkCoordIntPair pair : chunkList) {
this.c(pair.x, pair.z).a(entityplayer);
}
// CraftBukkit end
this.managedPlayers.add(entityplayer);
this.e();
}
public void removePlayer(EntityPlayer entityplayer) {
int i = (int) entityplayer.d >> 4;
int j = (int) entityplayer.e >> 4;
// Paper start - Player view distance API
int viewDistance = entityplayer.getViewDistance();
for (int k = i - viewDistance; k <= i + viewDistance; ++k) {
for (int l = j - viewDistance; l <= j + viewDistance; ++l) {
// Paper end
PlayerChunk playerchunk = this.getChunk(k, l);
if (playerchunk != null) {
playerchunk.b(entityplayer);
}
}
}
this.managedPlayers.remove(entityplayer);
this.e();
}
private boolean a(int i, int j, int k, int l, int i1) {
int j1 = i - k;
int k1 = j - l;
return j1 >= -i1 && j1 <= i1 ? k1 >= -i1 && k1 <= i1 : false;
}
public void movePlayer(EntityPlayer entityplayer) {
int i = (int) entityplayer.locX >> 4;
int j = (int) entityplayer.locZ >> 4;
double d0 = entityplayer.d - entityplayer.locX;
double d1 = entityplayer.e - entityplayer.locZ;
double d2 = d0 * d0 + d1 * d1;
if (d2 >= 64.0D) {
int k = (int) entityplayer.d >> 4;
int l = (int) entityplayer.e >> 4;
final int viewDistance = entityplayer.getViewDistance(); // Paper - Player view distance API
int i1 = Math.max(getViewDistance(), viewDistance); // Paper - Player view distance API
int j1 = i - k;
int k1 = j - l;
List<ChunkCoordIntPair> chunksToLoad = new LinkedList<ChunkCoordIntPair>(); // CraftBukkit
if (j1 != 0 || k1 != 0) {
for (int l1 = i - i1; l1 <= i + i1; ++l1) {
for (int i2 = j - i1; i2 <= j + i1; ++i2) {
if (!this.a(l1, i2, k, l, viewDistance)) { // Paper - Player view distance API
// this.c(l1, i2).a(entityplayer);
chunksToLoad.add(new ChunkCoordIntPair(l1, i2)); // CraftBukkit
}
if (!this.a(l1 - j1, i2 - k1, i, j, i1)) {
PlayerChunk playerchunk = this.getChunk(l1 - j1, i2 - k1);
if (playerchunk != null) {
playerchunk.b(entityplayer);
}
}
}
}
entityplayer.d = entityplayer.locX;
entityplayer.e = entityplayer.locZ;
this.e();
// CraftBukkit start - send nearest chunks first
Collections.sort(chunksToLoad, new ChunkCoordComparator(entityplayer));
for (ChunkCoordIntPair pair : chunksToLoad) {
this.c(pair.x, pair.z).a(entityplayer);
}
// CraftBukkit end
}
}
}
public boolean a(EntityPlayer entityplayer, int i, int j) {
PlayerChunk playerchunk = this.getChunk(i, j);
return playerchunk != null && playerchunk.d(entityplayer) && playerchunk.e();
}
public final void setViewDistanceForAll(int viewDistance) { this.a(viewDistance); } // Paper - OBFHELPER
// Paper start - Separate into two methods
public void a(int i) {
i = MathHelper.clamp(i, 3, 32);
if (i != this.j) {
//int j = i - this.j;
ArrayList arraylist = Lists.newArrayList(this.managedPlayers);
Iterator iterator = arraylist.iterator();
while (iterator.hasNext()) {
EntityPlayer entityplayer = (EntityPlayer) iterator.next();
this.setViewDistance(entityplayer, i, false); // Paper - Split, don't mark sort pending, we'll handle it after
}
this.j = i;
this.e();
}
}
public void setViewDistance(EntityPlayer entityplayer, int i) {
this.setViewDistance(entityplayer, i, true); // Mark sort pending by default so we don't have to remember to do so all the time
}
// Copied from above with minor changes
public void setViewDistance(EntityPlayer entityplayer, int i, boolean markSort) {
i = MathHelper.clamp(i, 3, 32);
int oldViewDistance = entityplayer.getViewDistance();
if (i != oldViewDistance) {
int j = i - oldViewDistance;
int k = (int) entityplayer.locX >> 4;
int l = (int) entityplayer.locZ >> 4;
int i1;
int j1;
if (j > 0) {
for (i1 = k - i; i1 <= k + i; ++i1) {
for (j1 = l - i; j1 <= l + i; ++j1) {
PlayerChunk playerchunk = this.c(i1, j1);
if (!playerchunk.d(entityplayer)) {
playerchunk.a(entityplayer);
}
}
}
} else {
for (i1 = k - oldViewDistance; i1 <= k + oldViewDistance; ++i1) {
for (j1 = l - oldViewDistance; j1 <= l + oldViewDistance; ++j1) {
if (!this.a(i1, j1, k, l, i)) {
this.c(i1, j1).b(entityplayer);
}
}
}
if (markSort) {
this.e();
}
}
}
}
// Paper end
private void e() {
this.l = true;
this.m = true;
}
public static int getFurthestViewableBlock(int i) {
return i * 16 - 16;
}
/** Returns given chunk index */
public static long chunkIndex(int chunkX, int chunkZ) { return d(chunkX, chunkZ); } // OBFHELPER
private static long d(int i, int j) {
return i + 2147483647L | j + 2147483647L << 32;
}
public void a(PlayerChunk playerchunk) {
org.spigotmc.AsyncCatcher.catchOp("Async Player Chunk Add"); // Paper
this.f.add(playerchunk);
}
public void b(PlayerChunk playerchunk) {
org.spigotmc.AsyncCatcher.catchOp("Async Player Chunk Remove"); // Paper
ChunkCoordIntPair chunkcoordintpair = playerchunk.a();
long i = chunkIndex(chunkcoordintpair.x, chunkcoordintpair.z);
playerchunk.c();
this.e.remove(i);
this.i.remove(playerchunk);
this.f.remove(playerchunk);
this.g.remove(playerchunk);
this.h.remove(playerchunk);
Chunk chunk = playerchunk.f();
if (chunk != null) {
// Paper start - delay chunk unloads
if (world.paperConfig.delayChunkUnloadsBy <= 0) {
this.getWorld().getChunkProviderServer().unload(chunk);
} else {
chunk.scheduledForUnload = System.currentTimeMillis();
}
// Paper end
}
}
// CraftBukkit start - Sorter to load nearby chunks first
private static class ChunkCoordComparator implements java.util.Comparator<ChunkCoordIntPair> {
private int x;
private int z;
public ChunkCoordComparator (EntityPlayer entityplayer) {
x = (int) entityplayer.locX >> 4;
z = (int) entityplayer.locZ >> 4;
}
@Override
public int compare(ChunkCoordIntPair a, ChunkCoordIntPair b) {
if (a.equals(b)) {
return 0;
}
// Subtract current position to set center point
int ax = a.x - this.x;
int az = a.z - this.z;
int bx = b.x - this.x;
int bz = b.z - this.z;
int result = ((ax - bx) * (ax + bx)) + ((az - bz) * (az + bz));
if (result != 0) {
return result;
}
if (ax < 0) {
if (bx < 0) {
return bz - az;
} else {
return -1;
}
} else {
if (bx < 0) {
return 1;
} else {
return az - bz;
}
}
}
}
// CraftBukkit end
// Paper start - Player view distance API
public void updateViewDistance(EntityPlayer player, int toSet) {
final int oldViewDistance = player.getViewDistance();
int viewDistance = MathHelper.clamp(toSet, 3, 32);
if (toSet < 0) {
viewDistance = -1;
}
if (viewDistance != oldViewDistance) {
// Order matters
this.setViewDistance(player, viewDistance);
player.setViewDistance(viewDistance);
}
}
// Paper end
}