PaperMC believes that 1.16.2 is now ready for general release as we fixed the main issue plagueing the 1.16.x release, the MapLike data conversion issues. Until now, it was not safe for a server to convert a world to 1.16.2 without data conversion issues around villages and potentially other things. If you did, those MapLike errors meant something went wrong. This is now resolved. Big thanks to all those that helped, notably @BillyGalbreath and @Proximyst who did large parts of the update process with me. Please as always, backup your worlds and test before updating to 1.16.2! If you update to 1.16.2, there is no going back to an older build than this. --------------------------------- Co-authored-by: William Blake Galbreath <Blake.Galbreath@GMail.com> Co-authored-by: Mariell Hoversholm <proximyst@proximyst.com> Co-authored-by: krolik-exe <69214078+krolik-exe@users.noreply.github.com> Co-authored-by: BillyGalbreath <BillyGalbreath@users.noreply.github.com> Co-authored-by: stonar96 <minecraft.stonar96@gmail.com> Co-authored-by: Shane Freeder <theboyetronic@gmail.com> Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Co-authored-by: Riley Park <rileysebastianpark@gmail.com> Co-authored-by: Aurora <21148213+aurorasmiles@users.noreply.github.com> Co-authored-by: Nassim Jahnke <nassim@njahnke.dev> Co-authored-by: commandblockguy <commandblockguy1@gmail.com> Co-authored-by: DigitalRegent <misterwener@gmail.com> Co-authored-by: ishland <ishlandmc@yeah.net>
269 lines
15 KiB
Diff
269 lines
15 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Aikar <aikar@aikar.co>
|
|
Date: Sun, 19 Apr 2020 04:28:29 -0400
|
|
Subject: [PATCH] Load Chunks for Login Asynchronously
|
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
|
|
@@ -0,0 +0,0 @@ public class ChunkProviderServer extends IChunkProvider {
|
|
return this.serverThreadQueue.executeNext();
|
|
}
|
|
|
|
- private boolean tickDistanceManager() {
|
|
+ public boolean tickDistanceManager() { // Paper - private -> public
|
|
boolean flag = this.chunkMapDistance.a(this.playerChunkMap);
|
|
boolean flag1 = this.playerChunkMap.b();
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/Entity.java
|
|
+++ b/src/main/java/net/minecraft/server/Entity.java
|
|
@@ -0,0 +0,0 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
|
|
this.lastY = d1;
|
|
this.lastZ = d4;
|
|
this.setPosition(d3, d1, d4);
|
|
- world.getChunkAt((int) Math.floor(this.locX()) >> 4, (int) Math.floor(this.locZ()) >> 4); // CraftBukkit
|
|
+ if (valid) world.getChunkAt((int) Math.floor(this.locX()) >> 4, (int) Math.floor(this.locZ()) >> 4); // CraftBukkit // Paper
|
|
}
|
|
|
|
public void d(Vec3D vec3d) {
|
|
diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
|
|
@@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
public PlayerConnection playerConnection;
|
|
+ public NetworkManager networkManager; // Paper
|
|
public final MinecraftServer server;
|
|
public final PlayerInteractManager playerInteractManager;
|
|
public final Deque<Integer> removeQueue = new ArrayDeque<>(); // Paper
|
|
@@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
|
|
public boolean joining = true;
|
|
public boolean sentListPacket = false;
|
|
public boolean supressTrackerForLogin = false; // Paper
|
|
+ public boolean didPlayerJoinEvent = false; // Paper
|
|
public Integer clientViewDistance;
|
|
// CraftBukkit end
|
|
public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper
|
|
diff --git a/src/main/java/net/minecraft/server/LoginListener.java b/src/main/java/net/minecraft/server/LoginListener.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/LoginListener.java
|
|
+++ b/src/main/java/net/minecraft/server/LoginListener.java
|
|
@@ -0,0 +0,0 @@ public class LoginListener implements PacketLoginInListener {
|
|
}
|
|
// Paper end
|
|
} else if (this.g == LoginListener.EnumProtocolState.DELAY_ACCEPT) {
|
|
- EntityPlayer entityplayer = this.server.getPlayerList().getPlayer(this.i.getId());
|
|
+ EntityPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.i.getId()); // Paper
|
|
|
|
if (entityplayer == null) {
|
|
this.g = LoginListener.EnumProtocolState.READY_TO_ACCEPT;
|
|
@@ -0,0 +0,0 @@ public class LoginListener implements PacketLoginInListener {
|
|
}
|
|
|
|
this.networkManager.sendPacket(new PacketLoginOutSuccess(this.i));
|
|
- EntityPlayer entityplayer = this.server.getPlayerList().getPlayer(this.i.getId());
|
|
+ EntityPlayer entityplayer = this.server.getPlayerList().getActivePlayer(this.i.getId()); // Paper
|
|
|
|
if (entityplayer != null) {
|
|
this.g = LoginListener.EnumProtocolState.DELAY_ACCEPT;
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerConnection.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerConnection.java
|
|
@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn {
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
public final NetworkManager networkManager;
|
|
private final MinecraftServer minecraftServer;
|
|
+ Runnable playerJoinReady; // Paper
|
|
public EntityPlayer player;
|
|
private int e;
|
|
private long lastKeepAlive = SystemUtils.getMonotonicMillis(); private void setLastPing(long lastPing) { this.lastKeepAlive = lastPing;}; private long getLastPing() { return this.lastKeepAlive;}; // Paper - OBFHELPER
|
|
@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn {
|
|
// CraftBukkit end
|
|
|
|
public void tick() {
|
|
+ // Paper start - login async
|
|
+ Runnable playerJoinReady = this.playerJoinReady;
|
|
+ if (playerJoinReady != null) {
|
|
+ this.playerJoinReady = null;
|
|
+ playerJoinReady.run();
|
|
+ }
|
|
+ // Don't tick if not valid (dead), otherwise we load chunks below
|
|
+ if (this.player.valid) {
|
|
+ // Paper end
|
|
this.syncPosition();
|
|
this.player.lastX = this.player.locX();
|
|
this.player.lastY = this.player.locY();
|
|
@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn {
|
|
this.r = null;
|
|
this.D = false;
|
|
this.E = 0;
|
|
- }
|
|
+ }} // Paper - end if (valid)
|
|
|
|
this.minecraftServer.getMethodProfiler().enter("keepAlive");
|
|
// Paper Start - give clients a longer time to respond to pings as per pre 1.12.2 timings
|
|
diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/PlayerList.java
|
|
+++ b/src/main/java/net/minecraft/server/PlayerList.java
|
|
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
|
private static final SimpleDateFormat g = new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
|
|
private final MinecraftServer server;
|
|
public final List<EntityPlayer> players = new java.util.concurrent.CopyOnWriteArrayList(); // CraftBukkit - ArrayList -> CopyOnWriteArrayList: Iterator safety
|
|
- private final Map<UUID, EntityPlayer> j = Maps.newHashMap();
|
|
+ private final Map<UUID, EntityPlayer> j = Maps.newHashMap();Map<UUID, EntityPlayer> getUUIDMap() { return j; } // Paper - OBFHELPER
|
|
private final GameProfileBanList k;
|
|
private final IpBanList l;
|
|
private final OpList operators;
|
|
private final WhiteList whitelist;
|
|
+ private final Map<UUID, EntityPlayer> pendingPlayers = Maps.newHashMap(); // Paper
|
|
// CraftBukkit start
|
|
// private final Map<UUID, ServerStatisticManager> o;
|
|
// private final Map<UUID, AdvancementDataPlayer> p;
|
|
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
|
}
|
|
|
|
public void a(NetworkManager networkmanager, EntityPlayer entityplayer) {
|
|
+ EntityPlayer prev = pendingPlayers.put(entityplayer.getUniqueID(), entityplayer);// Paper
|
|
+ if (prev != null) {
|
|
+ disconnectPendingPlayer(prev);
|
|
+ }
|
|
+ entityplayer.networkManager = networkmanager; // Paper
|
|
entityplayer.loginTime = System.currentTimeMillis(); // Paper
|
|
GameProfile gameprofile = entityplayer.getProfile();
|
|
UserCache usercache = this.server.getUserCache();
|
|
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
|
if (nbttagcompound != null && nbttagcompound.hasKey("bukkit")) {
|
|
NBTTagCompound bukkit = nbttagcompound.getCompound("bukkit");
|
|
s = bukkit.hasKeyOfType("lastKnownName", 8) ? bukkit.getString("lastKnownName") : s;
|
|
- }
|
|
+ }String lastKnownName = s; // Paper
|
|
// CraftBukkit end
|
|
|
|
if (nbttagcompound != null) {
|
|
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
|
entityplayer.getRecipeBook().a(entityplayer);
|
|
this.sendScoreboard(worldserver1.getScoreboard(), entityplayer);
|
|
this.server.invalidatePingSample();
|
|
+ // Paper start - async load spawn in chunk
|
|
+ WorldServer finalWorldserver = worldserver1;
|
|
+ int chunkX = loc.getBlockX() >> 4;
|
|
+ int chunkZ = loc.getBlockZ() >> 4;
|
|
+ final ChunkCoordIntPair pos = new ChunkCoordIntPair(chunkX, chunkZ);
|
|
+ PlayerChunkMap playerChunkMap = worldserver1.getChunkProvider().playerChunkMap;
|
|
+ playerChunkMap.chunkDistanceManager.addTicketAtLevel(TicketType.LOGIN, pos, 31, pos.pair());
|
|
+ worldserver1.getChunkProvider().tickDistanceManager();
|
|
+ worldserver1.getChunkProvider().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> {
|
|
+ PlayerChunk updatingChunk = playerChunkMap.getUpdatingChunk(pos.pair());
|
|
+ if (updatingChunk != null) {
|
|
+ return updatingChunk.getEntityTickingFuture();
|
|
+ } else {
|
|
+ return java.util.concurrent.CompletableFuture.completedFuture(chunk);
|
|
+ }
|
|
+ }).thenAccept(chunk -> {
|
|
+ playerconnection.playerJoinReady = () -> {
|
|
+ postChunkLoadJoin(
|
|
+ entityplayer, finalWorldserver, networkmanager, playerconnection,
|
|
+ nbttagcompound, networkmanager.getSocketAddress().toString(), lastKnownName
|
|
+ );
|
|
+ };
|
|
+ });
|
|
+ }
|
|
+
|
|
+ EntityPlayer getActivePlayer(UUID uuid) {
|
|
+ EntityPlayer player = this.getUUIDMap().get(uuid);
|
|
+ return player != null ? player : pendingPlayers.get(uuid);
|
|
+ }
|
|
+
|
|
+ void disconnectPendingPlayer(EntityPlayer entityplayer) {
|
|
+ ChatMessage msg = new ChatMessage("multiplayer.disconnect.duplicate_login", new Object[0]);
|
|
+ entityplayer.networkManager.sendPacket(new PacketPlayOutKickDisconnect(msg), (future) -> {
|
|
+ entityplayer.networkManager.close(msg);
|
|
+ entityplayer.networkManager = null;
|
|
+ });
|
|
+ }
|
|
+
|
|
+ private void postChunkLoadJoin(EntityPlayer entityplayer, WorldServer worldserver1, NetworkManager networkmanager, PlayerConnection playerconnection, NBTTagCompound nbttagcompound, String s1, String s) {
|
|
+ pendingPlayers.remove(entityplayer.getUniqueID(), entityplayer);
|
|
+ if (!networkmanager.isConnected()) {
|
|
+ return;
|
|
+ }
|
|
+ entityplayer.didPlayerJoinEvent = true;
|
|
+ // Paper end
|
|
ChatMessage chatmessage;
|
|
|
|
if (entityplayer.getProfile().getName().equalsIgnoreCase(s)) {
|
|
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
|
|
|
protected void savePlayerFile(EntityPlayer entityplayer) {
|
|
if (!entityplayer.getBukkitEntity().isPersistent()) return; // CraftBukkit
|
|
+ if (!entityplayer.didPlayerJoinEvent) return; // Paper - If we never fired PJE, we disconnected during login. Data has not changed, and additionally, our saved vehicle is not loaded! If we save now, we will lose our vehicle (CraftBukkit bug)
|
|
this.playerFileData.save(entityplayer);
|
|
ServerStatisticManager serverstatisticmanager = (ServerStatisticManager) entityplayer.getStatisticManager(); // CraftBukkit
|
|
|
|
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
|
entityplayer.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper
|
|
|
|
PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(cserver.getPlayer(entityplayer), "\u00A7e" + entityplayer.getName() + " left the game");
|
|
- cserver.getPluginManager().callEvent(playerQuitEvent);
|
|
+ if (entityplayer.didPlayerJoinEvent) cserver.getPluginManager().callEvent(playerQuitEvent); // Paper - if we disconnected before join ever fired, don't fire quit
|
|
entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
|
|
|
|
if (server.isMainThread()) entityplayer.playerTick(); // SPIGOT-924 // Paper - don't tick during emergency shutdowns (Watchdog)
|
|
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
|
// this.p.remove(uuid);
|
|
// CraftBukkit end
|
|
}
|
|
+ // Paper start
|
|
+ entityplayer1 = pendingPlayers.get(uuid);
|
|
+ if (entityplayer1 == entityplayer) {
|
|
+ pendingPlayers.remove(uuid);
|
|
+ }
|
|
+ entityplayer.networkManager = null;
|
|
+ // Paper end
|
|
|
|
// CraftBukkit start
|
|
// this.sendAll(new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, new EntityPlayer[]{entityplayer}));
|
|
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
|
cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity());
|
|
// CraftBukkit end
|
|
|
|
- return playerQuitEvent.getQuitMessage(); // CraftBukkit
|
|
+ return entityplayer.didPlayerJoinEvent ? playerQuitEvent.getQuitMessage() : null; // CraftBukkit // Paper - don't print quit if we never printed join
|
|
}
|
|
|
|
// CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer
|
|
@@ -0,0 +0,0 @@ public abstract class PlayerList {
|
|
list.add(entityplayer);
|
|
}
|
|
}
|
|
+ // Paper start - check pending players too
|
|
+ entityplayer = pendingPlayers.get(uuid);
|
|
+ if (entityplayer != null) {
|
|
+ this.pendingPlayers.remove(uuid);
|
|
+ disconnectPendingPlayer(entityplayer);
|
|
+ }
|
|
+ // Paper end
|
|
|
|
Iterator iterator = list.iterator();
|
|
|
|
diff --git a/src/main/java/net/minecraft/server/TicketType.java b/src/main/java/net/minecraft/server/TicketType.java
|
|
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644
|
|
--- a/src/main/java/net/minecraft/server/TicketType.java
|
|
+++ b/src/main/java/net/minecraft/server/TicketType.java
|
|
@@ -0,0 +0,0 @@ public class TicketType<T> {
|
|
public static final TicketType<ChunkCoordIntPair> FORCED = a("forced", Comparator.comparingLong(ChunkCoordIntPair::pair));
|
|
public static final TicketType<ChunkCoordIntPair> LIGHT = a("light", Comparator.comparingLong(ChunkCoordIntPair::pair));
|
|
public static final TicketType<BlockPosition> PORTAL = a("portal", BaseBlockPosition::compareTo, 300);
|
|
+ public static final TicketType<Long> LOGIN = a("login", Long::compareTo, 100); // Paper
|
|
public static final TicketType<Integer> POST_TELEPORT = a("post_teleport", Integer::compareTo, 5);
|
|
public static final TicketType<ChunkCoordIntPair> UNKNOWN = a("unknown", Comparator.comparingLong(ChunkCoordIntPair::pair), 1);
|
|
public static final TicketType<Unit> PLUGIN = a("plugin", (a, b) -> 0); // CraftBukkit
|