From cc93f5eea4f6aa7beb6608f7b79a6d9c1d65a945 Mon Sep 17 00:00:00 2001 From: Timon Seidel Date: Sun, 30 Mar 2025 19:52:54 +0200 Subject: [PATCH] feat: improve tablist (#1532) * fix: setDisplayName in TabListEntry duplicating players on 1.7.10 (#1530) * feat: expose toggling hat layer in TabListEntry (#1531) --- .../api/proxy/player/TabList.java | 25 +++++++++++- .../api/proxy/player/TabListEntry.java | 40 +++++++++++++++++-- .../proxy/tablist/KeyedVelocityTabList.java | 2 +- .../proxy/tablist/VelocityTabList.java | 18 +++++++-- .../proxy/tablist/VelocityTabListEntry.java | 24 ++++++++++- .../tablist/VelocityTabListEntryLegacy.java | 4 +- .../proxy/tablist/VelocityTabListLegacy.java | 17 +++++++- 7 files changed, 118 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java b/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java index feffd76e..a4035530 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/TabList.java @@ -40,6 +40,7 @@ public interface TabList { * Adds a {@link TabListEntry} to the {@link Player}'s tab list. * * @param entry to add to the tab list + * @throws IllegalArgumentException on versions below 1.19.3, if an entry with the same UUID already exists */ void addEntry(TabListEntry entry); @@ -47,6 +48,7 @@ public interface TabList { * Adds a {@link Iterable} of {@link TabListEntry}'s to the {@link Player}'s tab list. * * @param entries to add to the tab list + * @throws IllegalArgumentException on versions below 1.19.3, if an entry with the same UUID already exists */ default void addEntries(Iterable entries) { for (TabListEntry entry : entries) { @@ -58,6 +60,7 @@ public interface TabList { * Adds an array of {@link TabListEntry}'s to the {@link Player}'s tab list. * * @param entries to add to the tab list + * @throws IllegalArgumentException on versions below 1.19.3, if an entry with the same UUID already exists */ default void addEntries(TabListEntry... entries) { for (TabListEntry entry : entries) { @@ -187,6 +190,26 @@ public interface TabList { * @deprecated Internal usage. Use {@link TabListEntry.Builder} instead. */ @Deprecated + default TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, + int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder) { + return buildEntry(profile, displayName, latency, gameMode, chatSession, listed, listOrder, true); + } + + /** + * Represents an entry in a {@link Player}'s tab list. + * + * @param profile the profile + * @param displayName the display name + * @param latency the latency + * @param gameMode the game mode + * @param chatSession the chat session + * @param listed the visible status of entry + * @param listOrder the order/priority of entry in the tab list + * @param showHat the visibility of this entry's hat layer + * @return the entry + * @deprecated Internal usage. Use {@link TabListEntry.Builder} instead. + */ + @Deprecated TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, - int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder); + int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder, boolean showHat); } diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java b/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java index 350dc896..aea45287 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/TabListEntry.java @@ -160,6 +160,27 @@ public interface TabListEntry extends KeyIdentifiable { return this; } + /** + * Returns whether this entry's hat layer is shown in the tab list. + * + * @return whether to show this entry's hat layer + * @sinceMinecraft 1.21.4 + */ + default boolean isShowHat() { + return true; + } + + /** + * Sets whether to show this entry's hat layer in the tab list. + * + * @param showHat whether to show this entry's hat layer + * @return {@code this}, for chaining + * @sinceMinecraft 1.21.4 + */ + default TabListEntry setShowHat(boolean showHat) { + return this; + } + /** * Returns a {@link Builder} to create a {@link TabListEntry}. * @@ -183,6 +204,7 @@ public interface TabListEntry extends KeyIdentifiable { private int gameMode = 0; private boolean listed = true; private int listOrder = 0; + private boolean showHat; private @Nullable ChatSession chatSession; @@ -268,7 +290,7 @@ public interface TabListEntry extends KeyIdentifiable { * Sets whether this entry should be visible. * * @param listed to set - * @return ${code this}, for chaining + * @return {@code this}, for chaining * @see TabListEntry#isListed() */ public Builder listed(boolean listed) { @@ -280,7 +302,7 @@ public interface TabListEntry extends KeyIdentifiable { * Sets the order/priority of this entry in the tab list. * * @param order to set - * @return ${code this}, for chaining + * @return {@code this}, for chaining * @sinceMinecraft 1.21.2 * @see TabListEntry#getListOrder() */ @@ -289,6 +311,18 @@ public interface TabListEntry extends KeyIdentifiable { return this; } + /** + * Sets whether this entry's hat layer should be shown in the tab list. + * + * @param showHat to set + * @return {@code this}, for chaining + * @see TabListEntry#isShowHat() + */ + public Builder showHat(boolean showHat) { + this.showHat = showHat; + return this; + } + /** * Constructs the {@link TabListEntry} specified by {@code this} {@link Builder}. * @@ -301,7 +335,7 @@ public interface TabListEntry extends KeyIdentifiable { if (profile == null) { throw new IllegalStateException("The GameProfile must be set when building a TabListEntry"); } - return tabList.buildEntry(profile, displayName, latency, gameMode, chatSession, listed, listOrder); + return tabList.buildEntry(profile, displayName, latency, gameMode, chatSession, listed, listOrder, showHat); } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java index daaa9b0a..644a7603 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/KeyedVelocityTabList.java @@ -166,7 +166,7 @@ public class KeyedVelocityTabList implements InternalTabList { @Override public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, - int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder) { + int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder, boolean showHat) { return buildEntry(profile, displayName, latency, gameMode, chatSession, listed); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java index 0990119c..249bd3b7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabList.java @@ -90,7 +90,7 @@ public class VelocityTabList implements InternalTabList { } else { entry = new VelocityTabListEntry(this, entry1.getProfile(), entry1.getDisplayNameComponent().orElse(null), - entry1.getLatency(), entry1.getGameMode(), entry1.getChatSession(), entry1.isListed(), entry1.getListOrder()); + entry1.getLatency(), entry1.getGameMode(), entry1.getChatSession(), entry1.isListed(), entry1.getListOrder(), entry1.isShowHat()); } EnumSet actions = EnumSet @@ -134,6 +134,11 @@ public class VelocityTabList implements InternalTabList { actions.add(UpsertPlayerInfoPacket.Action.UPDATE_LIST_ORDER); playerInfoEntry.setListOrder(entry.getListOrder()); } + if (!Objects.equals(previousEntry.isShowHat(), entry.isShowHat()) + && player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_4)) { + actions.add(UpsertPlayerInfoPacket.Action.UPDATE_HAT); + playerInfoEntry.setShowHat(entry.isShowHat()); + } if (!Objects.equals(previousEntry.getChatSession(), entry.getChatSession())) { ChatSession from = entry.getChatSession(); if (from != null) { @@ -173,6 +178,10 @@ public class VelocityTabList implements InternalTabList { actions.add(UpsertPlayerInfoPacket.Action.UPDATE_LIST_ORDER); playerInfoEntry.setListOrder(entry.getListOrder()); } + if (!entry.isShowHat() && player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_4)) { + actions.add(UpsertPlayerInfoPacket.Action.UPDATE_HAT); + playerInfoEntry.setShowHat(entry.isShowHat()); + } } return entry; }); @@ -218,9 +227,9 @@ public class VelocityTabList implements InternalTabList { @Override public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode, - @Nullable ChatSession chatSession, boolean listed, int listOrder) { + @Nullable ChatSession chatSession, boolean listed, int listOrder, boolean showHat) { return new VelocityTabListEntry(this, profile, displayName, latency, gameMode, chatSession, - listed, listOrder); + listed, listOrder, showHat); } @Override @@ -258,7 +267,8 @@ public class VelocityTabList implements InternalTabList { -1, null, false, - 0 + 0, + true ) ); } else { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java index 352d6271..96592dc2 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntry.java @@ -40,6 +40,7 @@ public class VelocityTabListEntry implements TabListEntry { private int gameMode; private boolean listed; private int listOrder; + private boolean showHat; private @Nullable ChatSession session; /** @@ -47,7 +48,7 @@ public class VelocityTabListEntry implements TabListEntry { */ public VelocityTabListEntry(VelocityTabList tabList, GameProfile profile, Component displayName, int latency, - int gameMode, @Nullable ChatSession session, boolean listed, int listOrder) { + int gameMode, @Nullable ChatSession session, boolean listed, int listOrder, boolean showHat) { this.tabList = tabList; this.profile = profile; this.displayName = displayName; @@ -56,6 +57,7 @@ public class VelocityTabListEntry implements TabListEntry { this.session = session; this.listed = listed; this.listOrder = listOrder; + this.showHat = showHat; } @Override @@ -173,4 +175,24 @@ public class VelocityTabListEntry implements TabListEntry { void setListOrderWithoutUpdate(int listOrder) { this.listOrder = listOrder; } + + @Override + public boolean isShowHat() { + return showHat; + } + + @Override + public VelocityTabListEntry setShowHat(boolean showHat) { + this.showHat = showHat; + if (tabList.getPlayer().getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_4)) { + UpsertPlayerInfoPacket.Entry upsertEntry = this.tabList.createRawEntry(this); + upsertEntry.setShowHat(showHat); + tabList.emitActionRaw(UpsertPlayerInfoPacket.Action.UPDATE_HAT, upsertEntry); + } + return this; + } + + void setShowHatWithoutUpdate(boolean showHat) { + this.showHat = showHat; + } } \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntryLegacy.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntryLegacy.java index cd5e58db..56418d9d 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntryLegacy.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListEntryLegacy.java @@ -35,6 +35,8 @@ public class VelocityTabListEntryLegacy extends KeyedVelocityTabListEntry { @Override public TabListEntry setDisplayName(@Nullable Component displayName) { getTabList().removeEntry(getProfile().getId()); // We have to remove first if updating - return super.setDisplayName(displayName); + setDisplayNameInternal(displayName); + getTabList().addEntry(this); + return this; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java index 1eeb180e..a49d24de 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/tablist/VelocityTabListLegacy.java @@ -19,6 +19,8 @@ package com.velocitypowered.proxy.tablist; import com.google.common.collect.ImmutableList; import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.crypto.IdentifiedKey; +import com.velocitypowered.api.proxy.player.ChatSession; import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; @@ -133,9 +135,22 @@ public class VelocityTabListLegacy extends KeyedVelocityTabList { } } + @Override + public TabListEntry buildEntry(GameProfile profile, + net.kyori.adventure.text.@Nullable Component displayName, + int latency, int gameMode, @Nullable IdentifiedKey key) { + return new VelocityTabListEntryLegacy(this, profile, displayName, latency, gameMode); + } + @Override public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, - int gameMode) { + int gameMode, @Nullable ChatSession chatSession, boolean listed) { + return new VelocityTabListEntryLegacy(this, profile, displayName, latency, gameMode); + } + + @Override + public TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, + int gameMode, @Nullable ChatSession chatSession, boolean listed, int listOrder, boolean showHat) { return new VelocityTabListEntryLegacy(this, profile, displayName, latency, gameMode); } }