diff --git a/patches/api/0091-Add-extended-PaperServerListPingEvent.patch b/patches/api/0091-Add-extended-PaperServerListPingEvent.patch index 967c0ad0e0..b3c41b89b9 100644 --- a/patches/api/0091-Add-extended-PaperServerListPingEvent.patch +++ b/patches/api/0091-Add-extended-PaperServerListPingEvent.patch @@ -8,20 +8,28 @@ and allows full control of the response sent to the client. diff --git a/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java b/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java new file mode 100644 -index 0000000000000000000000000000000000000000..0bb6fdbdd05ae6a8fb413e0f6b8d84302d697267 +index 0000000000000000000000000000000000000000..acff2ff570f8419ffa4dfefe890795c63d75325d --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java -@@ -0,0 +1,338 @@ +@@ -0,0 +1,502 @@ +package com.destroystokyo.paper.event.server; + +import static java.util.Objects.requireNonNull; + +import com.destroystokyo.paper.network.StatusClient; +import com.destroystokyo.paper.profile.PlayerProfile; ++import com.destroystokyo.paper.profile.ProfileProperty; ++import com.google.common.base.Preconditions; ++import io.papermc.paper.util.TransformingRandomAccessList; ++import java.util.Collection; ++import java.util.Map; ++import java.util.Set; ++import java.util.concurrent.CompletableFuture; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.server.ServerListPingEvent; ++import org.bukkit.profile.PlayerTextures; +import org.bukkit.util.CachedServerIcon; + +import java.util.ArrayList; @@ -44,7 +52,12 @@ index 0000000000000000000000000000000000000000..0bb6fdbdd05ae6a8fb413e0f6b8d8430 + + private int numPlayers; + private boolean hidePlayers; -+ @NotNull private final List playerSample = new ArrayList<>(); ++ @NotNull private final List listedPlayers = new ArrayList<>(); ++ @NotNull private final TransformingRandomAccessList playerSample = new TransformingRandomAccessList<>( ++ listedPlayers, ++ info -> new UncheckedPlayerProfile(info.name(), info.id()), ++ profile -> new ListedPlayerInfo(profile.getName(), profile.getId()) ++ ); + + @NotNull private String version; + private int protocolVersion; @@ -163,7 +176,7 @@ index 0000000000000000000000000000000000000000..0bb6fdbdd05ae6a8fb413e0f6b8d8430 + } + + /** -+ * Returns a mutable list of {@link PlayerProfile} that will be displayed ++ * Returns a mutable list of {@link ListedPlayerInfo} that will be displayed + * as online players on the client. + *

+ * The Vanilla Minecraft client will display them when hovering the @@ -172,6 +185,22 @@ index 0000000000000000000000000000000000000000..0bb6fdbdd05ae6a8fb413e0f6b8d8430 + * @return The mutable player sample list + */ + @NotNull ++ public List getListedPlayers() { ++ return this.listedPlayers; ++ } ++ ++ /** ++ * Returns a mutable list of {@link PlayerProfile} that will be displayed ++ * as online players on the client. ++ *

++ * The Vanilla Minecraft client will display them when hovering the ++ * player count with the mouse. ++ * ++ * @return The mutable player sample list ++ * @deprecated Use {@link #getListedPlayers()}, as this does not contain real player profiles ++ */ ++ @NotNull ++ @Deprecated(forRemoval = true, since = "1.20.6") + public List getPlayerSample() { + return this.playerSample; + } @@ -276,9 +305,11 @@ index 0000000000000000000000000000000000000000..0bb6fdbdd05ae6a8fb413e0f6b8d8430 + *

  • Remove all entries from {@link #getPlayerSample()} that refer to + * the removed player (based on their {@link UUID}).
  • + * ++ * @deprecated the Iterable interface will be removed at some point + */ + @NotNull + @Override ++ @Deprecated(forRemoval = true, since = "1.20.6") + public Iterator iterator() { + if (this.players == null) { + this.players = getOnlinePlayers(); @@ -349,6 +380,139 @@ index 0000000000000000000000000000000000000000..0bb6fdbdd05ae6a8fb413e0f6b8d8430 + } + } + ++ /** ++ * Represents a player that will be displayed in the player sample of the server list. ++ * ++ * @param name name of the listed player ++ * @param id UUID of the listed player ++ */ ++ public record ListedPlayerInfo(@NotNull String name, @NotNull UUID id) { ++ } ++ ++ @ApiStatus.Internal ++ private static final class UncheckedPlayerProfile implements PlayerProfile { ++ private String name; ++ private UUID uuid; ++ ++ public UncheckedPlayerProfile(final @NotNull String name, final @NotNull UUID uuid) { ++ Preconditions.checkNotNull(name, "name cannot be null"); ++ Preconditions.checkNotNull(uuid, "uuid cannot be null"); ++ this.name = name; ++ this.uuid = uuid; ++ } ++ ++ @Override ++ public @Nullable UUID getUniqueId() { ++ return uuid; ++ } ++ ++ @Override ++ public @Nullable String getName() { ++ return name; ++ } ++ ++ @Override ++ public @NotNull String setName(@Nullable final String name) { ++ Preconditions.checkNotNull(name, "name cannot be null"); ++ return this.name = name; ++ } ++ ++ @Override ++ public @Nullable UUID getId() { ++ return uuid; ++ } ++ ++ @Override ++ public @Nullable UUID setId(@Nullable final UUID uuid) { ++ Preconditions.checkNotNull(uuid, "uuid cannot be null"); ++ return this.uuid = uuid; ++ } ++ ++ @Override ++ public @NotNull PlayerTextures getTextures() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setTextures(@Nullable final PlayerTextures textures) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull Set getProperties() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public boolean hasProperty(@Nullable final String property) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setProperty(@NotNull final ProfileProperty property) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void setProperties(@NotNull final Collection properties) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public boolean removeProperty(@Nullable final String property) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void clearProperties() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public boolean isComplete() { ++ return false; ++ } ++ ++ @Override ++ public boolean completeFromCache() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public boolean completeFromCache(final boolean onlineMode) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public boolean completeFromCache(final boolean lookupUUID, final boolean onlineMode) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public boolean complete(final boolean textures) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public boolean complete(final boolean textures, final boolean onlineMode) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull CompletableFuture update() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public org.bukkit.profile.@NotNull PlayerProfile clone() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull Map serialize() { ++ throw new UnsupportedOperationException(); ++ } ++ } +} diff --git a/src/main/java/com/destroystokyo/paper/network/StatusClient.java b/src/main/java/com/destroystokyo/paper/network/StatusClient.java new file mode 100644 @@ -369,6 +533,22 @@ index 0000000000000000000000000000000000000000..517d15238ed117f38bbd39f570874014 +public interface StatusClient extends NetworkClient { + +} +diff --git a/src/main/java/org/bukkit/event/server/ServerListPingEvent.java b/src/main/java/org/bukkit/event/server/ServerListPingEvent.java +index 72ebc29db42d08d1d0361dba462fc8a573fbf918..d351f62971f8f1317b3fc92f8b1d446e48149c3a 100644 +--- a/src/main/java/org/bukkit/event/server/ServerListPingEvent.java ++++ b/src/main/java/org/bukkit/event/server/ServerListPingEvent.java +@@ -248,9 +248,11 @@ public class ServerListPingEvent extends ServerEvent implements Iterable + * + * @throws UnsupportedOperationException if the caller of this event does + * not support removing players ++ * @deprecated the Iterable interface will be removed at some point + */ + @NotNull + @Override ++ @Deprecated(forRemoval = true, since = "1.20.6") + public Iterator iterator() throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } diff --git a/src/main/java/org/bukkit/util/CachedServerIcon.java b/src/main/java/org/bukkit/util/CachedServerIcon.java index 9a7768d41270714d4a1c89b4dcb436cc66f57545..b74b21a1ac7798e847b6d34ff45026e1c9cfed14 100644 --- a/src/main/java/org/bukkit/util/CachedServerIcon.java diff --git a/patches/server/0175-Implement-extended-PaperServerListPingEvent.patch b/patches/server/0175-Implement-extended-PaperServerListPingEvent.patch index 1884316112..293bebe913 100644 --- a/patches/server/0175-Implement-extended-PaperServerListPingEvent.patch +++ b/patches/server/0175-Implement-extended-PaperServerListPingEvent.patch @@ -60,23 +60,18 @@ index 0000000000000000000000000000000000000000..d926ad804355ee2fdc5910b2505e8671 +} diff --git a/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java b/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java new file mode 100644 -index 0000000000000000000000000000000000000000..69433be355f284030ca79cba509a351bb32ccd8b +index 0000000000000000000000000000000000000000..aa2aff62c873ba85b0cbced5382398c858420e59 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java -@@ -0,0 +1,116 @@ +@@ -0,0 +1,97 @@ +package com.destroystokyo.paper.network; + -+import com.destroystokyo.paper.profile.CraftPlayerProfile; -+import com.destroystokyo.paper.profile.PlayerProfile; -+import com.google.common.base.MoreObjects; -+import com.google.common.base.Strings; +import com.mojang.authlib.GameProfile; +import io.papermc.paper.adventure.AdventureComponent; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; -+import java.util.UUID; +import javax.annotation.Nonnull; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; @@ -87,9 +82,6 @@ index 0000000000000000000000000000000000000000..69433be355f284030ca79cba509a351b + +public final class StandardPaperServerListPingEventImpl extends PaperServerListPingEventImpl { + -+ // private static final GameProfile[] EMPTY_PROFILES = new GameProfile[0]; -+ private static final UUID FAKE_UUID = new UUID(0, 0); -+ + private List originalSample; + + private StandardPaperServerListPingEventImpl(MinecraftServer server, Connection networkManager, ServerStatus ping) { @@ -99,12 +91,12 @@ index 0000000000000000000000000000000000000000..69433be355f284030ca79cba509a351b + + @Nonnull + @Override -+ public List getPlayerSample() { -+ List sample = super.getPlayerSample(); ++ public List getListedPlayers() { ++ List sample = super.getListedPlayers(); + + if (this.originalSample != null) { + for (GameProfile profile : this.originalSample) { -+ sample.add(CraftPlayerProfile.asBukkitCopy(profile)); ++ sample.add(new ListedPlayerInfo(profile.getName(), profile.getId())); + } + this.originalSample = null; + } @@ -117,25 +109,14 @@ index 0000000000000000000000000000000000000000..69433be355f284030ca79cba509a351b + return this.originalSample; + } + -+ List entries = super.getPlayerSample(); ++ List entries = super.getListedPlayers(); + if (entries.isEmpty()) { + return Collections.emptyList(); + } + + final List profiles = new ArrayList<>(); -+ for (PlayerProfile profile : entries) { -+ /* -+ * Avoid null UUIDs/names since that will make the response invalid -+ * on the client. -+ * Instead, fall back to a fake/empty UUID and an empty string as name. -+ * This can be used to create custom lines in the player list that do not -+ * refer to a specific player. -+ */ -+ if (profile.getId() != null && profile.getName() != null) { -+ profiles.add(CraftPlayerProfile.asAuthlib(profile)); -+ } else { -+ profiles.add(new GameProfile(MoreObjects.firstNonNull(profile.getId(), FAKE_UUID), Strings.nullToEmpty(profile.getName()))); -+ } ++ for (ListedPlayerInfo playerInfo : entries) { ++ profiles.add(new GameProfile(playerInfo.id(), playerInfo.name())); + } + return profiles; + }