diff --git a/paper-api/src/main/java/io/papermc/paper/connection/PlayerCommonConnection.java b/paper-api/src/main/java/io/papermc/paper/connection/PlayerCommonConnection.java index 7cba94e4916b..5e374fa34955 100644 --- a/paper-api/src/main/java/io/papermc/paper/connection/PlayerCommonConnection.java +++ b/paper-api/src/main/java/io/papermc/paper/connection/PlayerCommonConnection.java @@ -43,4 +43,29 @@ public interface PlayerCommonConnection extends WritablePlayerCookieConnection, * @return the client option value of the player */ T getClientOption(ClientOption type); + + /** + * Gets the player's estimated ping in milliseconds. + *

+ * In Vanilla this value represents a weighted average of the response time + * to application layer ping packets sent. This value does not represent the + * network round trip time and as such may have less granularity and be + * impacted by other sources. For these reasons it should not be used + * for anti-cheat purposes. Its recommended use is only as a + * qualitative indicator of connection quality (Vanilla uses it for + * this purpose in the tab list). + * + * @return player ping + */ + int getPing(); + + /** + * Gets the player's most recent measured ping. + *

+ * This differs from {@link #getPing()} as it represents an average of ping over time, + * whereas this represents simply the most recent ping. + * + * @return player's most recent ping + */ + int getLastPing(); } diff --git a/paper-api/src/main/java/org/bukkit/entity/Player.java b/paper-api/src/main/java/org/bukkit/entity/Player.java index 3eef8d533c2a..379f0fbf9fd4 100644 --- a/paper-api/src/main/java/org/bukkit/entity/Player.java +++ b/paper-api/src/main/java/org/bukkit/entity/Player.java @@ -3359,6 +3359,7 @@ default void spawnParticle(Particle particle, Location location, int count, */ java.util.Locale locale(); // Paper end + /** * Gets the player's estimated ping in milliseconds. * diff --git a/paper-server/patches/features/0024-Improve-keepalive-ping-system.patch b/paper-server/patches/features/0024-Improve-keepalive-ping-system.patch index f046496ff6a4..5f260ebf6138 100644 --- a/paper-server/patches/features/0024-Improve-keepalive-ping-system.patch +++ b/paper-server/patches/features/0024-Improve-keepalive-ping-system.patch @@ -100,10 +100,10 @@ index 21d50675bfe90c2276890779eb23de58ac915b9a..7be34e37562875313b8e43357921b5fe } } diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 079ab378920c0e52ef4e42ef20b37bd389f40870..b8a4b4cc02a2fc6b70f4b840796eed501aad6239 100644 +index 0fd13e18902991d7ad4cbed0222d91cb71229d7a..a3768425e63722c8b0a99579a680f4cd9a9055c2 100644 --- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -39,12 +39,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -39,13 +39,14 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack protected final MinecraftServer server; public final Connection connection; // Paper private final boolean transferred; @@ -117,19 +117,21 @@ index 079ab378920c0e52ef4e42ef20b37bd389f40870..b8a4b4cc02a2fc6b70f4b840796eed50 private boolean closed = false; - private int latency; + private volatile int latency; // Paper - improve keepalives - make volatile + public int lastLatency; // Paper - Keep most recent latency in millis + private final io.papermc.paper.util.KeepAlive keepAlive; // Paper - improve keepalives private volatile boolean suspendFlushingOnServerThread = false; // CraftBukkit start public final org.bukkit.craftbukkit.CraftServer cserver; -@@ -61,13 +62,14 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -62,7 +63,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) { this.server = server; this.connection = connection; - this.keepAliveTime = Util.getMillis(); + //this.keepAliveTime = Util.getMillis(); // Paper - improve keepalives this.latency = cookie.latency(); + this.lastLatency = this.latency; // Paper - Keep most recent latency in millis this.transferred = cookie.transferred(); - // Paper start +@@ -70,6 +71,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack this.playerBrand = cookie.brandName(); this.cserver = server.server; this.pluginMessagerChannels = cookie.channels(); @@ -137,13 +139,14 @@ index 079ab378920c0e52ef4e42ef20b37bd389f40870..b8a4b4cc02a2fc6b70f4b840796eed50 // Paper end } -@@ -100,13 +102,41 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -102,14 +104,42 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack @Override public void handleKeepAlive(ServerboundKeepAlivePacket packet) { - if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) { - int i = (int)(Util.getMillis() - this.keepAliveTime); - this.latency = (this.latency * 3 + i) / 4; +- this.lastLatency = i; // Paper - Keep most recent latency in millis - this.keepAlivePending = false; - } else if (!this.isSingleplayerOwner()) { - this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, io.papermc.paper.connection.DisconnectionReason.TIMEOUT); // Paper - add proper async disconnect @@ -159,6 +162,7 @@ index 079ab378920c0e52ef4e42ef20b37bd389f40870..b8a4b4cc02a2fc6b70f4b840796eed50 + this.keepAlive.pingCalculator5s.update(response); + + this.latency = this.keepAlive.pingCalculator5s.getAvgLatencyMS(); ++ this.lastLatency = (int) java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(response.latencyNS()); // Paper - Keep most recent latency in millis + return; + } + @@ -185,7 +189,7 @@ index 079ab378920c0e52ef4e42ef20b37bd389f40870..b8a4b4cc02a2fc6b70f4b840796eed50 } @Override -@@ -233,20 +263,23 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -236,20 +266,23 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack protected void keepConnectionAlive() { Profiler.get().push("keepAlive"); long millis = Util.getMillis(); @@ -223,7 +227,7 @@ index 079ab378920c0e52ef4e42ef20b37bd389f40870..b8a4b4cc02a2fc6b70f4b840796eed50 } } -@@ -427,6 +460,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -430,6 +463,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack } protected CommonListenerCookie createCookie(ClientInformation clientInformation) { diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch index 0f41a4c322e1..0f681a201d16 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch @@ -9,9 +9,11 @@ private final boolean transferred; private long keepAliveTime; private boolean keepAlivePending; -@@ -46,6 +_,17 @@ +@@ -45,15 +_,39 @@ + private long closedListenerTime; private boolean closed = false; private int latency; ++ public int lastLatency; // Paper - Keep most recent latency in millis private volatile boolean suspendFlushingOnServerThread = false; + // CraftBukkit start + public final org.bukkit.craftbukkit.CraftServer cserver; @@ -27,9 +29,10 @@ public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) { this.server = server; -@@ -53,7 +_,18 @@ + this.connection = connection; this.keepAliveTime = Util.getMillis(); this.latency = cookie.latency(); ++ this.lastLatency = this.latency; // Paper - Keep most recent latency in millis this.transferred = cookie.transferred(); + // Paper start + this.playerBrand = cookie.brandName(); @@ -46,8 +49,11 @@ private void close() { if (!this.closed) { -@@ -83,7 +_,7 @@ +@@ -81,9 +_,10 @@ + if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) { + int i = (int)(Util.getMillis() - this.keepAliveTime); this.latency = (this.latency * 3 + i) / 4; ++ this.lastLatency = i; // Paper - Keep most recent latency in millis this.keepAlivePending = false; } else if (!this.isSingleplayerOwner()) { - this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE); diff --git a/paper-server/src/main/java/io/papermc/paper/connection/PaperCommonConnection.java b/paper-server/src/main/java/io/papermc/paper/connection/PaperCommonConnection.java index 9593df35b29c..245df37e9ac5 100644 --- a/paper-server/src/main/java/io/papermc/paper/connection/PaperCommonConnection.java +++ b/paper-server/src/main/java/io/papermc/paper/connection/PaperCommonConnection.java @@ -22,26 +22,26 @@ public abstract class PaperCommonConnection extends ReadablePlayerCookieConnectionImpl implements PlayerCommonConnection { - protected final T handle; + protected final T packetListener; - public PaperCommonConnection(final T serverConfigurationPacketListenerImpl) { - super(serverConfigurationPacketListenerImpl.connection); - this.handle = serverConfigurationPacketListenerImpl; + public PaperCommonConnection(final T packetListener) { + super(packetListener.connection); + this.packetListener = packetListener; } @Override public void sendReportDetails(final Map details) { - this.handle.send(new ClientboundCustomReportDetailsPacket(details)); + this.packetListener.send(new ClientboundCustomReportDetailsPacket(details)); } @Override public void sendLinks(final ServerLinks links) { - this.handle.send(new ClientboundServerLinksPacket(((CraftServerLinks) links).getServerLinks().untrust())); + this.packetListener.send(new ClientboundServerLinksPacket(((CraftServerLinks) links).getServerLinks().untrust())); } @Override public void transfer(final String host, final int port) { - this.handle.send(new ClientboundTransferPacket(host, port)); + this.packetListener.send(new ClientboundTransferPacket(host, port)); } @Override @@ -72,32 +72,32 @@ public T getClientOption(ClientOption type) { @Override public void disconnect(final Component component) { - this.handle.disconnect(PaperAdventure.asVanilla(component), DisconnectionReason.UNKNOWN); + this.packetListener.disconnect(PaperAdventure.asVanilla(component), DisconnectionReason.UNKNOWN); } @Override public boolean isTransferred() { - return this.handle.isTransferred(); + return this.packetListener.isTransferred(); } @Override public SocketAddress getAddress() { - return this.handle.connection.channel.remoteAddress(); + return this.packetListener.connection.channel.remoteAddress(); } @Override public InetSocketAddress getClientAddress() { - return (InetSocketAddress) this.handle.connection.getRemoteAddress(); + return (InetSocketAddress) this.packetListener.connection.getRemoteAddress(); } @Override public @Nullable InetSocketAddress getVirtualHost() { - return this.handle.connection.virtualHost; + return this.packetListener.connection.virtualHost; } @Override public @Nullable InetSocketAddress getHAProxyAddress() { - return this.handle.connection.haProxyAddress instanceof final InetSocketAddress inetSocketAddress ? inetSocketAddress : null; + return this.packetListener.connection.haProxyAddress instanceof final InetSocketAddress inetSocketAddress ? inetSocketAddress : null; } @Override @@ -107,7 +107,17 @@ public void storeCookie(final NamespacedKey key, final byte[] value) { Preconditions.checkArgument(value.length <= 5120, "Cookie value too large, must be smaller than 5120 bytes"); Preconditions.checkState(this.canStoreCookie(), "Can only store cookie in CONFIGURATION or PLAY protocol."); - this.handle.send(new ClientboundStoreCookiePacket(CraftNamespacedKey.toMinecraft(key), value)); + this.packetListener.send(new ClientboundStoreCookiePacket(CraftNamespacedKey.toMinecraft(key), value)); + } + + @Override + public int getPing() { + return this.packetListener.latency(); + } + + @Override + public int getLastPing() { + return this.packetListener.lastLatency; } public abstract ClientInformation getClientInformation(); diff --git a/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerConfigurationConnection.java b/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerConfigurationConnection.java index c91f926da39d..49ea407cd912 100644 --- a/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerConfigurationConnection.java +++ b/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerConfigurationConnection.java @@ -47,7 +47,7 @@ public PaperPlayerConfigurationConnection(final ServerConfigurationPacketListene @Override public ClientInformation getClientInformation() { - return this.handle.clientInformation; + return this.packetListener.clientInformation; } @Override @@ -61,39 +61,39 @@ public void sendResourcePacks(final ResourcePackRequest request) { final ResourcePackInfo pack = iter.next(); packs.add(new ClientboundResourcePackPushPacket(pack.id(), pack.uri().toASCIIString(), pack.hash(), request.required(), iter.hasNext() ? Optional.empty() : Optional.ofNullable(prompt))); if (request.callback() != ResourcePackCallback.noOp()) { - this.handle.packCallbacks.put(pack.id(), request.callback()); // just override if there is a previously existing callback + this.packetListener.packCallbacks.put(pack.id(), request.callback()); // just override if there is a previously existing callback } } - packs.forEach(this.handle::send); + packs.forEach(this.packetListener::send); } @Override public void removeResourcePacks(final UUID id, final UUID... others) { - net.kyori.adventure.util.MonkeyBars.nonEmptyArrayToList(pack -> new ClientboundResourcePackPopPacket(Optional.of(pack)), id, others).forEach(this.handle::send); + net.kyori.adventure.util.MonkeyBars.nonEmptyArrayToList(pack -> new ClientboundResourcePackPopPacket(Optional.of(pack)), id, others).forEach(this.packetListener::send); } @Override public void clearResourcePacks() { - this.handle.send(new ClientboundResourcePackPopPacket(Optional.empty())); + this.packetListener.send(new ClientboundResourcePackPopPacket(Optional.empty())); } @Override public void showDialog(final DialogLike dialog) { - this.handle.send(new ClientboundShowDialogPacket(PaperDialog.bukkitToMinecraftHolder((Dialog) dialog))); + this.packetListener.send(new ClientboundShowDialogPacket(PaperDialog.bukkitToMinecraftHolder((Dialog) dialog))); } @Override public void closeDialog() { - this.handle.send(ClientboundClearDialogPacket.INSTANCE); + this.packetListener.send(ClientboundClearDialogPacket.INSTANCE); } @Override public Pointers pointers() { if (this.adventurePointers == null) { this.adventurePointers = Pointers.builder() - .withDynamic(Identity.NAME, () -> this.handle.getOwner().name()) - .withDynamic(Identity.UUID, () -> this.handle.getOwner().id()) - .withDynamic(Identity.LOCALE, () -> Objects.requireNonNullElse(Translator.parseLocale(this.handle.clientInformation.language()), Locale.US)) + .withDynamic(Identity.NAME, () -> this.packetListener.getOwner().name()) + .withDynamic(Identity.UUID, () -> this.packetListener.getOwner().id()) + .withDynamic(Identity.LOCALE, () -> Objects.requireNonNullElse(Translator.parseLocale(this.packetListener.clientInformation.language()), Locale.US)) .build(); } @@ -107,40 +107,40 @@ public Audience getAudience() { @Override public PlayerProfile getProfile() { - return CraftPlayerProfile.asBukkitCopy(this.handle.getOwner()); + return CraftPlayerProfile.asBukkitCopy(this.packetListener.getOwner()); } @Override public void clearChat() { - this.handle.send(ClientboundResetChatPacket.INSTANCE); + this.packetListener.send(ClientboundResetChatPacket.INSTANCE); } @Override public void completeReconfiguration() { - final ConfigurationTask task = this.handle.currentTask; + final ConfigurationTask task = this.packetListener.currentTask; if (task != null) { // This means that the player is going through the normal configuration process, or is already returning to the game phase. // Be safe and just ignore, as many plugins may call this. return; } - this.handle.returnToWorld(); + this.packetListener.returnToWorld(); } @Override public Set channels() { - return this.handle.pluginMessagerChannels; + return this.packetListener.pluginMessagerChannels; } @Override public void sendPluginMessage(final Plugin source, final String channel, final byte[] message) { - StandardMessenger.validatePluginMessage(this.handle.cserver.getMessenger(), source, channel, message); + StandardMessenger.validatePluginMessage(this.packetListener.cserver.getMessenger(), source, channel, message); if (this.channels().contains(channel)) { @SuppressWarnings("deprecation") // "not an API method" does not apply to us Identifier id = Identifier.parse(StandardMessenger.validateAndCorrectChannel(channel)); ClientboundCustomPayloadPacket packet = new ClientboundCustomPayloadPacket(new DiscardedPayload(id, message)); - this.handle.send(packet); + this.packetListener.send(packet); } } @@ -151,6 +151,6 @@ public Set getListeningPluginChannels() { @Override public boolean isConnected() { - return this.handle.connection.isConnected(); + return this.packetListener.connection.isConnected(); } } diff --git a/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerGameConnection.java b/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerGameConnection.java index 1a06f8ffc45e..56cdc7f06eb0 100644 --- a/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerGameConnection.java +++ b/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerGameConnection.java @@ -8,26 +8,26 @@ public class PaperPlayerGameConnection extends PaperCommonConnection implements PlayerGameConnection { - public PaperPlayerGameConnection(final ServerGamePacketListenerImpl serverConfigurationPacketListenerImpl) { - super(serverConfigurationPacketListenerImpl); + public PaperPlayerGameConnection(final ServerGamePacketListenerImpl packetListener) { + super(packetListener); } @Override public ClientInformation getClientInformation() { - return this.handle.player.clientInformation(); + return this.packetListener.player.clientInformation(); } @Override public void reenterConfiguration() { - if (HorriblePlayerLoginEventHack.warnReenterConfiguration(this.handle.connection)) { + if (HorriblePlayerLoginEventHack.warnReenterConfiguration(this.packetListener.connection)) { return; } - this.handle.switchToConfig(); + this.packetListener.switchToConfig(); } @Override public Player getPlayer() { - return this.handle.getCraftPlayer(); + return this.packetListener.getCraftPlayer(); } @Override