From 7fc2316b0c052c5c56a6492d848b6e0547dc1241 Mon Sep 17 00:00:00 2001 From: William Date: Thu, 13 Jun 2024 11:24:51 +0100 Subject: [PATCH 1/9] feat: add basic server links API --- .../com/velocitypowered/api/proxy/Player.java | 11 +++ .../velocitypowered/api/util/ServerLink.java | 91 +++++++++++++++++++ .../connection/client/ConnectedPlayer.java | 22 +++++ 3 files changed, 124 insertions(+) create mode 100644 api/src/main/java/com/velocitypowered/api/util/ServerLink.java diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index dfe9a2bc72..6f14e7d071 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -21,6 +21,7 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.ModInfo; +import com.velocitypowered.api.util.ServerLink; import java.net.InetSocketAddress; import java.util.Collection; import java.util.List; @@ -461,4 +462,14 @@ default void openBook(@NotNull Book book) { * @sinceMinecraft 1.20.5 */ void requestCookie(Key key); + + /** + * Send the player a list of custom links to display in their client's pause menu. + * + * @param links an ordered list of {@link ServerLink}s to send to the player + * @throws IllegalArgumentException if the player is from a version lower than 1.21 + * @since 3.3.0 + * @sinceMinecraft 1.21 + */ + void setServerLinks(List links); } \ No newline at end of file diff --git a/api/src/main/java/com/velocitypowered/api/util/ServerLink.java b/api/src/main/java/com/velocitypowered/api/util/ServerLink.java new file mode 100644 index 0000000000..190cf0d1cc --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/util/ServerLink.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2021-2024 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.util; + +import java.util.Optional; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a custom URL servers can show in player pause menus. + * Links can be of a built-in type or use a custom component text label. + */ +public final class ServerLink { + + private @Nullable Type type; + private @Nullable Component label; + private final String link; + + /** + * Construct a server link with a custom component label. + * + * @param label the label to display + * @param link the URL to open when clicked + */ + public ServerLink(Component label, String link) { + this.label = label; + this.link = link; + } + + /** + * Construct a server link with a built-in type. + * + * @param type the {@link Type type} of link + * @param link the URL to open when clicked + */ + public ServerLink(Type type, String link) { + this.type = type; + this.link = link; + } + + /** + * Get the type of the server link. + * + * @return the type of the server link + */ + public Optional getBuiltInType() { + return Optional.ofNullable(type); + } + + /** + * Get the link URL. + * + * @return the link URL + */ + public String getLink() { + return link; + } + + /** + * Get the custom component label of the server link. + * + * @return the custom component label of the server link + */ + public Optional getCustomLabel() { + return Optional.ofNullable(label); + } + + /** + * Built-in types of server links. + * + * @apiNote {@link Type#BUG_REPORT} links are shown on the connection error screen + */ + public enum Type { + BUG_REPORT, + COMMUNITY_GUIDELINES, + SUPPORT, + STATUS, + FEEDBACK, + COMMUNITY, + WEBSITE, + FORUMS, + NEWS, + ANNOUNCEMENTS + } + +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index a471e2b531..5befb09ec0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -54,6 +54,7 @@ import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.ModInfo; +import com.velocitypowered.api.util.ServerLink; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.adventure.VelocityBossBarImplementation; import com.velocitypowered.proxy.connection.MinecraftConnection; @@ -81,6 +82,7 @@ import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletionPacket; import com.velocitypowered.proxy.protocol.packet.chat.builder.ChatBuilderFactory; import com.velocitypowered.proxy.protocol.packet.chat.legacy.LegacyChatPacket; +import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket; import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket; import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket; import com.velocitypowered.proxy.protocol.util.ByteBufDataOutput; @@ -1057,6 +1059,26 @@ public void requestCookie(final Key key) { }, connection.eventLoop()); } + + @Override + public void setServerLinks(List links) { + Preconditions.checkNotNull(links); + Preconditions.checkArgument( + this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21), + "Player version must be at least 1.21 to be able to set server links"); + + if (connection.getState() != StateRegistry.PLAY + && connection.getState() != StateRegistry.CONFIG) { + throw new IllegalStateException("Can only send server links in CONFIGURATION or PLAY protocol"); + } + + connection.write(new ClientboundServerLinksPacket(links.stream() + .map(l -> new ClientboundServerLinksPacket.ServerLink( + l.getBuiltInType().map(Enum::ordinal).orElse(-1), + l.getCustomLabel().map(c -> new ComponentHolder(getProtocolVersion(), c)).orElse(null), + l.getLink())).toList())); + } + @Override public void addCustomChatCompletions(@NotNull Collection completions) { Preconditions.checkNotNull(completions, "completions"); From 6fab618d5bbf216c094cf3f3b8982578b056a48d Mon Sep 17 00:00:00 2001 From: William Date: Thu, 13 Jun 2024 11:32:41 +0100 Subject: [PATCH 2/9] refactor: more precondition checking on links --- .../com/velocitypowered/api/util/ServerLink.java | 14 ++++++++------ .../proxy/connection/client/ConnectedPlayer.java | 3 ++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/util/ServerLink.java b/api/src/main/java/com/velocitypowered/api/util/ServerLink.java index 190cf0d1cc..30a72b8875 100644 --- a/api/src/main/java/com/velocitypowered/api/util/ServerLink.java +++ b/api/src/main/java/com/velocitypowered/api/util/ServerLink.java @@ -7,6 +7,8 @@ package com.velocitypowered.api.util; +import com.google.common.base.Preconditions; +import java.net.URI; import java.util.Optional; import net.kyori.adventure.text.Component; import org.jetbrains.annotations.Nullable; @@ -27,9 +29,9 @@ public final class ServerLink { * @param label the label to display * @param link the URL to open when clicked */ - public ServerLink(Component label, String link) { - this.label = label; - this.link = link; + public ServerLink(Component label, String link) throws IllegalArgumentException { + this.label = Preconditions.checkNotNull(label, "label"); + this.link = URI.create(link).toString(); } /** @@ -38,9 +40,9 @@ public ServerLink(Component label, String link) { * @param type the {@link Type type} of link * @param link the URL to open when clicked */ - public ServerLink(Type type, String link) { - this.type = type; - this.link = link; + public ServerLink(Type type, String link) throws IllegalArgumentException { + this.type = Preconditions.checkNotNull(type, "type"); + this.link = URI.create(link).toString(); } /** diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 5befb09ec0..8d22e56727 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -23,6 +23,7 @@ import static java.util.concurrent.CompletableFuture.completedFuture; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.gson.JsonObject; import com.velocitypowered.api.event.connection.DisconnectEvent; import com.velocitypowered.api.event.connection.DisconnectEvent.LoginStatus; @@ -1072,7 +1073,7 @@ public void setServerLinks(List links) { throw new IllegalStateException("Can only send server links in CONFIGURATION or PLAY protocol"); } - connection.write(new ClientboundServerLinksPacket(links.stream() + connection.write(new ClientboundServerLinksPacket(ImmutableList.copyOf(links).stream() .map(l -> new ClientboundServerLinksPacket.ServerLink( l.getBuiltInType().map(Enum::ordinal).orElse(-1), l.getCustomLabel().map(c -> new ComponentHolder(getProtocolVersion(), c)).orElse(null), From ab6052bfcb0f153fda5ac56c3c4611d8d38c20b1 Mon Sep 17 00:00:00 2001 From: William Date: Thu, 13 Jun 2024 11:33:18 +0100 Subject: [PATCH 3/9] refactor: remove whitespace --- .../velocitypowered/proxy/connection/client/ConnectedPlayer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 8d22e56727..691d116b55 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -1060,7 +1060,6 @@ public void requestCookie(final Key key) { }, connection.eventLoop()); } - @Override public void setServerLinks(List links) { Preconditions.checkNotNull(links); From fc87c18ec3e8a6eef9388cc94af392cdb797a50b Mon Sep 17 00:00:00 2001 From: William Date: Thu, 13 Jun 2024 11:38:20 +0100 Subject: [PATCH 4/9] refactor: adjust method order, JDs in ServerLink --- .../velocitypowered/api/util/ServerLink.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/util/ServerLink.java b/api/src/main/java/com/velocitypowered/api/util/ServerLink.java index 30a72b8875..96627c8f07 100644 --- a/api/src/main/java/com/velocitypowered/api/util/ServerLink.java +++ b/api/src/main/java/com/velocitypowered/api/util/ServerLink.java @@ -26,7 +26,7 @@ public final class ServerLink { /** * Construct a server link with a custom component label. * - * @param label the label to display + * @param label a custom component label to display * @param link the URL to open when clicked */ public ServerLink(Component label, String link) throws IllegalArgumentException { @@ -37,7 +37,7 @@ public ServerLink(Component label, String link) throws IllegalArgumentException /** * Construct a server link with a built-in type. * - * @param type the {@link Type type} of link + * @param type the {@link Type built-in type} of link * @param link the URL to open when clicked */ public ServerLink(Type type, String link) throws IllegalArgumentException { @@ -55,21 +55,21 @@ public Optional getBuiltInType() { } /** - * Get the link URL. + * Get the custom component label of the server link. * - * @return the link URL + * @return the custom component label of the server link */ - public String getLink() { - return link; + public Optional getCustomLabel() { + return Optional.ofNullable(label); } /** - * Get the custom component label of the server link. + * Get the link URL. * - * @return the custom component label of the server link + * @return the link URL */ - public Optional getCustomLabel() { - return Optional.ofNullable(label); + public String getLink() { + return link; } /** From f88143f9cfb46e76d21119f840e64f3533b8ef73 Mon Sep 17 00:00:00 2001 From: William Date: Thu, 13 Jun 2024 11:39:49 +0100 Subject: [PATCH 5/9] refactor: remove "throws" from constructors --- .../main/java/com/velocitypowered/api/util/ServerLink.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/util/ServerLink.java b/api/src/main/java/com/velocitypowered/api/util/ServerLink.java index 96627c8f07..f9a39af7ee 100644 --- a/api/src/main/java/com/velocitypowered/api/util/ServerLink.java +++ b/api/src/main/java/com/velocitypowered/api/util/ServerLink.java @@ -29,7 +29,7 @@ public final class ServerLink { * @param label a custom component label to display * @param link the URL to open when clicked */ - public ServerLink(Component label, String link) throws IllegalArgumentException { + public ServerLink(Component label, String link) { this.label = Preconditions.checkNotNull(label, "label"); this.link = URI.create(link).toString(); } @@ -40,7 +40,7 @@ public ServerLink(Component label, String link) throws IllegalArgumentException * @param type the {@link Type built-in type} of link * @param link the URL to open when clicked */ - public ServerLink(Type type, String link) throws IllegalArgumentException { + public ServerLink(Type type, String link) { this.type = Preconditions.checkNotNull(type, "type"); this.link = URI.create(link).toString(); } From 9f236d751474d8af91bcf4bdbed812e8901c7838 Mon Sep 17 00:00:00 2001 From: William Date: Thu, 13 Jun 2024 11:42:28 +0100 Subject: [PATCH 6/9] refactor: add @NotNull annotations --- api/src/main/java/com/velocitypowered/api/proxy/Player.java | 2 +- .../proxy/connection/client/ConnectedPlayer.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index 6f14e7d071..328993f5ba 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -471,5 +471,5 @@ default void openBook(@NotNull Book book) { * @since 3.3.0 * @sinceMinecraft 1.21 */ - void setServerLinks(List links); + void setServerLinks(@NotNull List links); } \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 691d116b55..d4fa8db491 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -1061,8 +1061,8 @@ public void requestCookie(final Key key) { } @Override - public void setServerLinks(List links) { - Preconditions.checkNotNull(links); + public void setServerLinks(final @NotNull List links) { + Preconditions.checkNotNull(links, "links"); Preconditions.checkArgument( this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21), "Player version must be at least 1.21 to be able to set server links"); From 11966e96566a66c65027123ac7cbfa8ed1b8ce81 Mon Sep 17 00:00:00 2001 From: William Date: Thu, 13 Jun 2024 17:02:22 +0100 Subject: [PATCH 7/9] refactor: requested changes --- .../com/velocitypowered/api/proxy/Player.java | 1 + .../velocitypowered/api/util/ServerLink.java | 30 ++++++++++++------- .../connection/client/ConnectedPlayer.java | 5 +--- .../config/ClientboundServerLinksPacket.java | 8 +++++ 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index 328993f5ba..591ed7aab8 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -465,6 +465,7 @@ default void openBook(@NotNull Book book) { /** * Send the player a list of custom links to display in their client's pause menu. + *

Note that later packets sent by the backend server may override links sent by the proxy. * * @param links an ordered list of {@link ServerLink}s to send to the player * @throws IllegalArgumentException if the player is from a version lower than 1.21 diff --git a/api/src/main/java/com/velocitypowered/api/util/ServerLink.java b/api/src/main/java/com/velocitypowered/api/util/ServerLink.java index f9a39af7ee..d63fc36acd 100644 --- a/api/src/main/java/com/velocitypowered/api/util/ServerLink.java +++ b/api/src/main/java/com/velocitypowered/api/util/ServerLink.java @@ -21,7 +21,17 @@ public final class ServerLink { private @Nullable Type type; private @Nullable Component label; - private final String link; + private final URI url; + + private ServerLink(Component label, String url) { + this.label = Preconditions.checkNotNull(label, "label"); + this.url = URI.create(url); + } + + private ServerLink(Type type, String url) { + this.type = Preconditions.checkNotNull(type, "type"); + this.url = URI.create(url); + } /** * Construct a server link with a custom component label. @@ -29,9 +39,8 @@ public final class ServerLink { * @param label a custom component label to display * @param link the URL to open when clicked */ - public ServerLink(Component label, String link) { - this.label = Preconditions.checkNotNull(label, "label"); - this.link = URI.create(link).toString(); + public static ServerLink serverLink(Component label, String link) { + return new ServerLink(label, link); } /** @@ -40,9 +49,8 @@ public ServerLink(Component label, String link) { * @param type the {@link Type built-in type} of link * @param link the URL to open when clicked */ - public ServerLink(Type type, String link) { - this.type = Preconditions.checkNotNull(type, "type"); - this.link = URI.create(link).toString(); + public static ServerLink serverLink(Type type, String link) { + return new ServerLink(type, link); } /** @@ -64,12 +72,12 @@ public Optional getCustomLabel() { } /** - * Get the link URL. + * Get the link {@link URI}. * - * @return the link URL + * @return the link {@link URI} */ - public String getLink() { - return link; + public URI getUrl() { + return url; } /** diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index d4fa8db491..0c65d06ae7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -1073,10 +1073,7 @@ public void setServerLinks(final @NotNull List links) { } connection.write(new ClientboundServerLinksPacket(ImmutableList.copyOf(links).stream() - .map(l -> new ClientboundServerLinksPacket.ServerLink( - l.getBuiltInType().map(Enum::ordinal).orElse(-1), - l.getCustomLabel().map(c -> new ComponentHolder(getProtocolVersion(), c)).orElse(null), - l.getLink())).toList())); + .map(l -> new ClientboundServerLinksPacket.ServerLink(l, getProtocolVersion())).toList())); } @Override diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java index bee080ee82..274bbb8f9e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java @@ -18,6 +18,7 @@ package com.velocitypowered.proxy.protocol.packet.config; import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.util.ServerLink; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; @@ -66,6 +67,13 @@ public List getServerLinks() { } public record ServerLink(int id, ComponentHolder displayName, String url) { + + public ServerLink(com.velocitypowered.api.util.ServerLink link, ProtocolVersion protocolVersion) { + this(link.getBuiltInType().map(Enum::ordinal).orElse(-1), + link.getCustomLabel().map(c -> new ComponentHolder(protocolVersion, c)).orElse(null), + link.getUrl().toString()); + } + private static ServerLink read(ByteBuf buf, ProtocolVersion version) { if (buf.readBoolean()) { return new ServerLink(ProtocolUtils.readVarInt(buf), null, ProtocolUtils.readString(buf)); From de2c8f90546df99244c2d41e5f6e5e281a669c1a Mon Sep 17 00:00:00 2001 From: William Date: Thu, 13 Jun 2024 17:10:36 +0100 Subject: [PATCH 8/9] fix: checkstyle --- api/src/main/java/com/velocitypowered/api/proxy/Player.java | 1 + api/src/main/java/com/velocitypowered/api/util/ServerLink.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/Player.java b/api/src/main/java/com/velocitypowered/api/proxy/Player.java index 591ed7aab8..04e65c849b 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/Player.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/Player.java @@ -465,6 +465,7 @@ default void openBook(@NotNull Book book) { /** * Send the player a list of custom links to display in their client's pause menu. + * *

Note that later packets sent by the backend server may override links sent by the proxy. * * @param links an ordered list of {@link ServerLink}s to send to the player diff --git a/api/src/main/java/com/velocitypowered/api/util/ServerLink.java b/api/src/main/java/com/velocitypowered/api/util/ServerLink.java index d63fc36acd..9eb04a9801 100644 --- a/api/src/main/java/com/velocitypowered/api/util/ServerLink.java +++ b/api/src/main/java/com/velocitypowered/api/util/ServerLink.java @@ -50,7 +50,7 @@ public static ServerLink serverLink(Component label, String link) { * @param link the URL to open when clicked */ public static ServerLink serverLink(Type type, String link) { - return new ServerLink(type, link); + return new ServerLink(type, link); } /** From a00ab46136e160a2248eacdc2177fbebbdb196a6 Mon Sep 17 00:00:00 2001 From: William Date: Fri, 14 Jun 2024 11:10:30 +0100 Subject: [PATCH 9/9] refactor: just use `List#copyOf` --- .../proxy/connection/client/ConnectedPlayer.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 0c65d06ae7..0ad7ada2a6 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -23,7 +23,6 @@ import static java.util.concurrent.CompletableFuture.completedFuture; import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; import com.google.gson.JsonObject; import com.velocitypowered.api.event.connection.DisconnectEvent; import com.velocitypowered.api.event.connection.DisconnectEvent.LoginStatus; @@ -1072,7 +1071,7 @@ public void setServerLinks(final @NotNull List links) { throw new IllegalStateException("Can only send server links in CONFIGURATION or PLAY protocol"); } - connection.write(new ClientboundServerLinksPacket(ImmutableList.copyOf(links).stream() + connection.write(new ClientboundServerLinksPacket(List.copyOf(links).stream() .map(l -> new ClientboundServerLinksPacket.ServerLink(l, getProtocolVersion())).toList())); }