REPLACE_3 = Map.of(
"?dp?", ":"
diff --git a/src/main/java/net/william278/velocitab/packet/PacketRegistration.java b/src/main/java/net/william278/velocitab/packet/PacketRegistration.java
index 631f950..2affa37 100644
--- a/src/main/java/net/william278/velocitab/packet/PacketRegistration.java
+++ b/src/main/java/net/william278/velocitab/packet/PacketRegistration.java
@@ -138,20 +138,31 @@ public final class PacketRegistration {
final MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
final MethodHandles.Lookup stateRegistryLookup = MethodHandles.privateLookupIn(StateRegistry.class, lookup);
- STATE_REGISTRY$clientBound = stateRegistryLookup.findGetter(StateRegistry.class, "clientbound", StateRegistry.PacketRegistry.class);
- STATE_REGISTRY$serverBound = stateRegistryLookup.findGetter(StateRegistry.class, "serverbound", StateRegistry.PacketRegistry.class);
+ STATE_REGISTRY$clientBound = stateRegistryLookup.findGetter(
+ StateRegistry.class, "clientbound", StateRegistry.PacketRegistry.class);
+ STATE_REGISTRY$serverBound = stateRegistryLookup.findGetter(
+ StateRegistry.class, "serverbound", StateRegistry.PacketRegistry.class);
- final MethodType mapType = MethodType.methodType(StateRegistry.PacketMapping.class, Integer.TYPE, ProtocolVersion.class, Boolean.TYPE);
- PACKET_MAPPING$map = stateRegistryLookup.findStatic(StateRegistry.class, "map", mapType);
+ final MethodType mapType = MethodType.methodType(
+ StateRegistry.PacketMapping.class, Integer.TYPE, ProtocolVersion.class, Boolean.TYPE);
+ PACKET_MAPPING$map = stateRegistryLookup.findStatic(
+ StateRegistry.class, "map", mapType);
- final MethodHandles.Lookup packetRegistryLookup = MethodHandles.privateLookupIn(StateRegistry.PacketRegistry.class, lookup);
- final MethodType registerType = MethodType.methodType(void.class, Class.class, Supplier.class, StateRegistry.PacketMapping[].class);
- PACKET_REGISTRY$register = packetRegistryLookup.findVirtual(StateRegistry.PacketRegistry.class, "register", registerType);
- PACKET_REGISTRY$versions = packetRegistryLookup.findGetter(StateRegistry.PacketRegistry.class, "versions", Map.class);
+ final MethodHandles.Lookup packetRegistryLookup = MethodHandles.privateLookupIn(
+ StateRegistry.PacketRegistry.class, lookup);
+ final MethodType registerType = MethodType.methodType(
+ void.class, Class.class, Supplier.class, StateRegistry.PacketMapping[].class);
+ PACKET_REGISTRY$register = packetRegistryLookup.findVirtual(
+ StateRegistry.PacketRegistry.class, "register", registerType);
+ PACKET_REGISTRY$versions = packetRegistryLookup.findGetter(
+ StateRegistry.PacketRegistry.class, "versions", Map.class);
- final MethodHandles.Lookup protocolRegistryLookup = MethodHandles.privateLookupIn(StateRegistry.PacketRegistry.ProtocolRegistry.class, lookup);
- PACKET_REGISTRY$packetIdToSupplier = protocolRegistryLookup.findGetter(StateRegistry.PacketRegistry.ProtocolRegistry.class, "packetIdToSupplier", IntObjectMap.class);
- PACKET_REGISTRY$packetClassToId = protocolRegistryLookup.findGetter(StateRegistry.PacketRegistry.ProtocolRegistry.class, "packetClassToId", Object2IntMap.class);
+ final MethodHandles.Lookup protocolRegistryLookup = MethodHandles.privateLookupIn(
+ StateRegistry.PacketRegistry.ProtocolRegistry.class, lookup);
+ PACKET_REGISTRY$packetIdToSupplier = protocolRegistryLookup.findGetter(
+ StateRegistry.PacketRegistry.ProtocolRegistry.class, "packetIdToSupplier", IntObjectMap.class);
+ PACKET_REGISTRY$packetClassToId = protocolRegistryLookup.findGetter(
+ StateRegistry.PacketRegistry.ProtocolRegistry.class, "packetClassToId", Object2IntMap.class);
} catch (Throwable e) {
diff --git a/src/main/java/net/william278/velocitab/packet/PlayerChannelHandler.java b/src/main/java/net/william278/velocitab/packet/PlayerChannelHandler.java
index 37c78e5..dce1432 100644
--- a/src/main/java/net/william278/velocitab/packet/PlayerChannelHandler.java
+++ b/src/main/java/net/william278/velocitab/packet/PlayerChannelHandler.java
@@ -45,8 +45,8 @@ public class PlayerChannelHandler extends ChannelDuplexHandler {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (msg instanceof final UpdateTeamsPacket updateTeamsPacket && plugin.getSettings().isSendScoreboardPackets()) {
- final Optional scoreboardManager = plugin.getScoreboardManager();
- if (scoreboardManager.isEmpty()) {
+ final ScoreboardManager scoreboardManager = plugin.getScoreboardManager();
+ if (!scoreboardManager.handleTeams()) {
super.write(ctx, msg, promise);
return;
}
@@ -56,7 +56,7 @@ public class PlayerChannelHandler extends ChannelDuplexHandler {
return;
}
- if (scoreboardManager.get().isInternalTeam(updateTeamsPacket.teamName())) {
+ if (scoreboardManager.isInternalTeam(updateTeamsPacket.teamName())) {
super.write(ctx, msg, promise);
return;
}
@@ -74,8 +74,8 @@ public class PlayerChannelHandler extends ChannelDuplexHandler {
// Cancel packet if the backend is trying to send a team packet with an online player.
// This is to prevent conflicts with Velocitab teams.
plugin.getLogger().warn("Cancelled team \"{}\" packet from backend for player {}. " +
- "We suggest disabling \"send_scoreboard_packets\" in Velocitab's config.yml file, " +
- "but note this will disable TAB sorting",
+ "We suggest disabling \"send_scoreboard_packets\" in Velocitab's config.yml file, " +
+ "but note this will disable TAB sorting",
updateTeamsPacket.teamName(), player.getUsername());
return;
}
diff --git a/src/main/java/net/william278/velocitab/packet/Protocol48Adapter.java b/src/main/java/net/william278/velocitab/packet/Protocol48Adapter.java
index f922456..a9d045e 100644
--- a/src/main/java/net/william278/velocitab/packet/Protocol48Adapter.java
+++ b/src/main/java/net/william278/velocitab/packet/Protocol48Adapter.java
@@ -104,6 +104,7 @@ public class Protocol48Adapter extends TeamsPacketAdapter {
/**
* Returns a shortened version of the given string, with a maximum length of 16 characters.
* This is used to ensure that the team name, display name, prefix and suffix are not too long for the client.
+ *
* @param string the string to be shortened
* @return the shortened string
*/
diff --git a/src/main/java/net/william278/velocitab/packet/Protocol765Adapter.java b/src/main/java/net/william278/velocitab/packet/Protocol765Adapter.java
index d80bc18..4817874 100644
--- a/src/main/java/net/william278/velocitab/packet/Protocol765Adapter.java
+++ b/src/main/java/net/william278/velocitab/packet/Protocol765Adapter.java
@@ -32,7 +32,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.Set;
/**
- * Adapter for handling the UpdateTeamsPacket for Minecraft 1.20.3-1.20.5
+ * Adapter for handling the UpdateTeamsPacket for Minecraft 1.20.3-1.21.2
*/
public class Protocol765Adapter extends Protocol404Adapter {
@@ -40,7 +40,8 @@ public class Protocol765Adapter extends Protocol404Adapter {
super(plugin, Set.of(
ProtocolVersion.MINECRAFT_1_20_3,
ProtocolVersion.MINECRAFT_1_20_5,
- ProtocolVersion.MINECRAFT_1_21
+ ProtocolVersion.MINECRAFT_1_21,
+ ProtocolVersion.MINECRAFT_1_21_2
));
}
@@ -51,7 +52,9 @@ public class Protocol765Adapter extends Protocol404Adapter {
@NotNull
protected Component readComponent(@NotNull ByteBuf buf) {
- return GsonComponentSerializer.gson().deserializeFromTree(ComponentHolder.deserialize(ProtocolUtils.readBinaryTag(buf, ProtocolVersion.MINECRAFT_1_20_3, null)));
+ return GsonComponentSerializer.gson().deserializeFromTree(ComponentHolder.deserialize(
+ ProtocolUtils.readBinaryTag(buf, ProtocolVersion.MINECRAFT_1_20_3, null)
+ ));
}
}
diff --git a/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java b/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java
index 6fa9690..9ee0dd3 100644
--- a/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java
+++ b/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java
@@ -30,18 +30,18 @@ import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
+import lombok.Getter;
import net.kyori.adventure.text.Component;
import net.william278.velocitab.Velocitab;
import net.william278.velocitab.config.Group;
import net.william278.velocitab.player.TabPlayer;
+import net.william278.velocitab.sorting.SortedSet;
import net.william278.velocitab.tab.Nametag;
import org.jetbrains.annotations.NotNull;
import org.slf4j.event.Level;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.UUID;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
import static com.velocitypowered.api.network.ProtocolVersion.*;
@@ -49,29 +49,43 @@ public class ScoreboardManager {
private PacketRegistration packetRegistration;
private final Velocitab plugin;
- private final Set versions;
+ private final boolean teams;
+ private final Map versions;
+ @Getter
private final Map createdTeams;
private final Map nametags;
private final Multimap trackedTeams;
+ @Getter
+ private final SortedSet sortedTeams;
- public ScoreboardManager(@NotNull Velocitab velocitab) {
+ public ScoreboardManager(@NotNull Velocitab velocitab, boolean teams) {
this.plugin = velocitab;
+ this.teams = teams;
this.createdTeams = Maps.newConcurrentMap();
this.nametags = Maps.newConcurrentMap();
- this.versions = Sets.newHashSet();
+ this.versions = Maps.newHashMap();
this.trackedTeams = Multimaps.synchronizedMultimap(Multimaps.newSetMultimap(Maps.newConcurrentMap(), Sets::newConcurrentHashSet));
+ this.sortedTeams = new SortedSet(Comparator.reverseOrder());
this.registerVersions();
}
+ public boolean handleTeams() {
+ return teams;
+ }
+
private void registerVersions() {
try {
- versions.add(new Protocol765Adapter(plugin));
- versions.add(new Protocol735Adapter(plugin));
- versions.add(new Protocol404Adapter(plugin));
- versions.add(new Protocol48Adapter(plugin));
+ final Protocol765Adapter protocol765Adapter = new Protocol765Adapter(plugin);
+ protocol765Adapter.getProtocolVersions().forEach(version -> versions.put(version, protocol765Adapter));
+ final Protocol735Adapter protocol735Adapter = new Protocol735Adapter(plugin);
+ protocol735Adapter.getProtocolVersions().forEach(version -> versions.put(version, protocol735Adapter));
+ final Protocol404Adapter protocol404Adapter = new Protocol404Adapter(plugin);
+ protocol404Adapter.getProtocolVersions().forEach(version -> versions.put(version, protocol404Adapter));
+ final Protocol48Adapter protocol48Adapter = new Protocol48Adapter(plugin);
+ protocol48Adapter.getProtocolVersions().forEach(version -> versions.put(version, protocol48Adapter));
} catch (NoSuchFieldError e) {
throw new IllegalStateException("Failed to register Scoreboard Teams packets." +
- " Velocitab probably does not (yet) support your Proxy version.", e);
+ " Velocitab probably does not (yet) support your Proxy version.", e);
}
}
@@ -79,11 +93,13 @@ public class ScoreboardManager {
return nametags.containsKey(teamName);
}
+ public int getPosition(@NotNull String teamName) {
+ return sortedTeams.getPosition(teamName);
+ }
+
@NotNull
public TeamsPacketAdapter getPacketAdapter(@NotNull ProtocolVersion version) {
- return versions.stream()
- .filter(adapter -> adapter.getProtocolVersions().contains(version))
- .findFirst()
+ return Optional.ofNullable(versions.get(version))
.orElseThrow(() -> new IllegalArgumentException("No adapter found for protocol version " + version));
}
@@ -94,6 +110,7 @@ public class ScoreboardManager {
public void resetCache(@NotNull Player player) {
final String team = createdTeams.remove(player.getUniqueId());
if (team != null) {
+ removeSortedTeam(team);
plugin.getTabList().getTabPlayer(player).ifPresent(tabPlayer ->
dispatchGroupPacket(UpdateTeamsPacket.removeTeam(plugin, team), tabPlayer)
);
@@ -104,10 +121,18 @@ public class ScoreboardManager {
public void resetCache(@NotNull Player player, @NotNull Group group) {
final String team = createdTeams.remove(player.getUniqueId());
if (team != null) {
+ removeSortedTeam(team);
dispatchGroupPacket(UpdateTeamsPacket.removeTeam(plugin, team), group);
}
}
+ private void removeSortedTeam(@NotNull String teamName) {
+ final boolean result = sortedTeams.removeTeam(teamName);
+ if (!result) {
+ plugin.log(Level.ERROR, "Failed to remove team " + teamName + " from sortedTeams");
+ }
+ }
+
public void vanishPlayer(@NotNull TabPlayer tabPlayer) {
this.handleVanish(tabPlayer, true);
}
@@ -127,12 +152,13 @@ public class ScoreboardManager {
return;
}
final Set siblings = tabPlayer.getGroup().registeredServers(plugin);
+ final boolean isNameTagEmpty = tabPlayer.getGroup().nametag().isEmpty();
final Optional cachedTag = Optional.ofNullable(nametags.getOrDefault(teamName, null));
cachedTag.ifPresent(nametag -> siblings.forEach(server -> server.getPlayersConnected().stream().filter(p -> p != player)
.forEach(connected -> {
if (vanish && !plugin.getVanishManager().canSee(connected.getUsername(), player.getUsername())) {
- dispatchPacket(UpdateTeamsPacket.removeTeam(plugin, teamName), connected);
+ sendPacket(connected, UpdateTeamsPacket.removeTeam(plugin, teamName), isNameTagEmpty);
trackedTeams.remove(connected.getUniqueId(), teamName);
} else {
dispatchGroupCreatePacket(plugin, tabPlayer, teamName, nametag, player.getUsername());
@@ -147,14 +173,14 @@ public class ScoreboardManager {
* @param role The new role of the player. Must not be null.
* @param force Whether to force the update even if the player's nametag is the same.
*/
- public void updateRole(@NotNull TabPlayer tabPlayer, @NotNull String role, boolean force) {
+ public CompletableFuture updateRole(@NotNull TabPlayer tabPlayer, @NotNull String role, boolean force) {
final Player player = tabPlayer.getPlayer();
if (!player.isActive()) {
plugin.getTabList().removeOfflinePlayer(player);
- return;
+ return CompletableFuture.completedFuture(null);
}
-
final String name = player.getUsername();
+ final CompletableFuture future = new CompletableFuture<>();
tabPlayer.getNametag(plugin).thenAccept(newTag -> {
if (!createdTeams.getOrDefault(player.getUniqueId(), "").equals(role)) {
if (createdTeams.containsKey(player.getUniqueId())) {
@@ -163,8 +189,15 @@ public class ScoreboardManager {
tabPlayer
);
}
-
+ final String oldRole = createdTeams.remove(player.getUniqueId());
+ if (oldRole != null) {
+ removeSortedTeam(oldRole);
+ }
createdTeams.put(player.getUniqueId(), role);
+ final boolean a = sortedTeams.addTeam(role);
+ if (!a) {
+ plugin.log(Level.ERROR, "Failed to add team " + role + " to sortedTeams");
+ }
this.nametags.put(role, newTag);
dispatchGroupCreatePacket(plugin, tabPlayer, role, newTag, name);
} else if (force || (this.nametags.containsKey(role) && !this.nametags.get(role).equals(newTag))) {
@@ -173,10 +206,13 @@ public class ScoreboardManager {
} else {
updatePlaceholders(tabPlayer);
}
+ future.complete(null);
}).exceptionally(e -> {
plugin.log(Level.ERROR, "Failed to update role for " + player.getUsername(), e);
return null;
});
+
+ return future;
}
public void updatePlaceholders(@NotNull TabPlayer tabPlayer) {
@@ -192,6 +228,9 @@ public class ScoreboardManager {
}
public void resendAllTeams(@NotNull TabPlayer tabPlayer) {
+ if (!teams) {
+ return;
+ }
if (!plugin.getSettings().isSendScoreboardPackets()) {
return;
}
@@ -238,6 +277,9 @@ public class ScoreboardManager {
private void dispatchGroupCreatePacket(@NotNull Velocitab plugin, @NotNull TabPlayer tabPlayer,
@NotNull String teamName, @NotNull Nametag nametag,
@NotNull String... teamMembers) {
+ if (!teams) {
+ return;
+ }
tabPlayer.getGroup().getTabPlayers(plugin, tabPlayer).forEach(viewer -> {
if (!viewer.getPlayer().isActive()) {
return;
@@ -252,6 +294,9 @@ public class ScoreboardManager {
@NotNull String teamName, @NotNull Nametag nametag,
@NotNull TabPlayer viewer,
@NotNull String... teamMembers) {
+ if (!teams) {
+ return;
+ }
final boolean canSee = plugin.getVanishManager().canSee(viewer.getPlayer().getUsername(), tabPlayer.getPlayer().getUsername());
if (!canSee) {
return;
@@ -259,12 +304,17 @@ public class ScoreboardManager {
final UpdateTeamsPacket packet = UpdateTeamsPacket.create(plugin, tabPlayer, teamName, nametag, viewer, teamMembers);
trackedTeams.put(viewer.getPlayer().getUniqueId(), teamName);
- dispatchPacket(packet, viewer.getPlayer());
+ final boolean isNameTagEmpty = tabPlayer.getGroup().nametag().isEmpty();
+ sendPacket(viewer.getPlayer(), packet, isNameTagEmpty);
}
private void dispatchGroupChangePacket(@NotNull Velocitab plugin, @NotNull TabPlayer tabPlayer,
@NotNull String teamName,
@NotNull Nametag nametag) {
+ if (!teams) {
+ return;
+ }
+ final boolean isNameTagEmpty = tabPlayer.getGroup().nametag().isEmpty();
tabPlayer.getGroup().getTabPlayers(plugin, tabPlayer).forEach(viewer -> {
if (viewer == tabPlayer || !viewer.getPlayer().isActive()) {
return;
@@ -289,30 +339,20 @@ public class ScoreboardManager {
return;
}
tabPlayer.setRelationalNametag(viewer.getPlayer().getUniqueId(), prefix, suffix);
- dispatchPacket(packet, viewer.getPlayer());
+ sendPacket(viewer.getPlayer(), packet, isNameTagEmpty);
});
}
- private void dispatchPacket(@NotNull UpdateTeamsPacket packet, @NotNull Player player) {
- if (!player.isActive()) {
- plugin.getTabList().removeOfflinePlayer(player);
+ private void dispatchGroupPacket(@NotNull UpdateTeamsPacket packet, @NotNull Group group) {
+ if (!teams) {
return;
}
- try {
- final ConnectedPlayer connectedPlayer = (ConnectedPlayer) player;
- connectedPlayer.getConnection().write(packet);
- } catch (Throwable e) {
- plugin.log(Level.ERROR, "Failed to dispatch packet (unsupported client or server version)", e);
- }
- }
-
- private void dispatchGroupPacket(@NotNull UpdateTeamsPacket packet, @NotNull Group group) {
final boolean isRemove = packet.isRemoveTeam();
+ final boolean isNameTagEmpty = group.nametag().isEmpty();
group.registeredServers(plugin).forEach(server -> server.getPlayersConnected().forEach(connected -> {
try {
- final ConnectedPlayer connectedPlayer = (ConnectedPlayer) connected;
- connectedPlayer.getConnection().write(packet);
+ sendPacket(connected, packet, isNameTagEmpty);
if (isRemove) {
trackedTeams.remove(connected.getUniqueId(), packet.teamName());
}
@@ -323,6 +363,9 @@ public class ScoreboardManager {
}
private void dispatchGroupPacket(@NotNull UpdateTeamsPacket packet, @NotNull TabPlayer tabPlayer) {
+ if (!teams) {
+ return;
+ }
final Player player = tabPlayer.getPlayer();
final Optional optionalServerConnection = player.getCurrentServer();
if (optionalServerConnection.isEmpty()) {
@@ -330,6 +373,7 @@ public class ScoreboardManager {
}
final Set players = tabPlayer.getGroup().getPlayers(plugin);
+ final boolean isNameTagEmpty = tabPlayer.getGroup().nametag().isEmpty();
players.forEach(connected -> {
try {
final boolean canSee = plugin.getVanishManager().canSee(connected.getUsername(), player.getUsername());
@@ -337,15 +381,30 @@ public class ScoreboardManager {
return;
}
- final ConnectedPlayer connectedPlayer = (ConnectedPlayer) connected;
- connectedPlayer.getConnection().write(packet);
+ sendPacket(connected, packet, isNameTagEmpty);
} catch (Throwable e) {
plugin.log(Level.ERROR, "Failed to dispatch packet (unsupported client or server version)", e);
}
});
}
+ private void sendPacket(@NotNull Player player, @NotNull UpdateTeamsPacket packet, boolean isNameTagEmpty) {
+ if (!player.isActive()) {
+ plugin.getTabList().removeOfflinePlayer(player);
+ return;
+ }
+ if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_2) && isNameTagEmpty) {
+ return;
+ }
+
+ final ConnectedPlayer connectedPlayer = (ConnectedPlayer) player;
+ connectedPlayer.getConnection().write(packet);
+ }
+
public void registerPacket() {
+ if (!teams) {
+ return;
+ }
try {
packetRegistration = PacketRegistration.of(UpdateTeamsPacket.class)
.direction(ProtocolUtils.Direction.CLIENTBOUND)
@@ -362,7 +421,8 @@ public class ScoreboardManager {
.mapping(0x5A, MINECRAFT_1_19_4, false)
.mapping(0x5C, MINECRAFT_1_20_2, false)
.mapping(0x5E, MINECRAFT_1_20_3, false)
- .mapping(0x60, MINECRAFT_1_20_5, false);
+ .mapping(0x60, MINECRAFT_1_20_5, false)
+ .mapping(0x67, MINECRAFT_1_21_2, false);
packetRegistration.register();
} catch (Throwable e) {
plugin.log(Level.ERROR, "Failed to register UpdateTeamsPacket", e);
@@ -389,6 +449,9 @@ public class ScoreboardManager {
* @param canSee A boolean indicating whether the player can see the target player.
*/
public void recalculateVanishForPlayer(TabPlayer tabPlayer, TabPlayer target, boolean canSee) {
+ if (!teams) {
+ return;
+ }
final Player player = tabPlayer.getPlayer();
final String team = createdTeams.get(target.getPlayer().getUniqueId());
if (team == null) {
@@ -396,7 +459,8 @@ public class ScoreboardManager {
}
final UpdateTeamsPacket removeTeam = UpdateTeamsPacket.removeTeam(plugin, team);
- dispatchPacket(removeTeam, player);
+ final boolean isNameTagEmpty = tabPlayer.getGroup().nametag().isEmpty();
+ sendPacket(player, removeTeam, isNameTagEmpty);
trackedTeams.remove(player.getUniqueId(), team);
if (canSee) {
diff --git a/src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java b/src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java
index 6e43de8..6bf6782 100644
--- a/src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java
+++ b/src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java
@@ -36,7 +36,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
-import java.util.Optional;
import java.util.stream.Collectors;
@Getter
@@ -208,22 +207,14 @@ public class UpdateTeamsPacket implements MinecraftPacket {
@Override
public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
- final Optional optionalManager = plugin.getScoreboardManager();
- if (optionalManager.isEmpty()) {
- return;
- }
-
- optionalManager.get().getPacketAdapter(protocolVersion).decode(byteBuf, this, protocolVersion);
+ final ScoreboardManager scoreboardManager = plugin.getScoreboardManager();
+ scoreboardManager.getPacketAdapter(protocolVersion).decode(byteBuf, this, protocolVersion);
}
@Override
public void encode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
- final Optional optionalManager = plugin.getScoreboardManager();
- if (optionalManager.isEmpty()) {
- return;
- }
-
- optionalManager.get().getPacketAdapter(protocolVersion).encode(byteBuf, this, protocolVersion);
+ final ScoreboardManager scoreboardManager = plugin.getScoreboardManager();
+ scoreboardManager.getPacketAdapter(protocolVersion).encode(byteBuf, this, protocolVersion);
}
@Override
diff --git a/src/main/java/net/william278/velocitab/player/TabPlayer.java b/src/main/java/net/william278/velocitab/player/TabPlayer.java
index b32a949..8e1648e 100644
--- a/src/main/java/net/william278/velocitab/player/TabPlayer.java
+++ b/src/main/java/net/william278/velocitab/player/TabPlayer.java
@@ -36,7 +36,10 @@ import org.apache.commons.lang3.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.*;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -58,10 +61,13 @@ public final class TabPlayer implements Comparable {
private final Map relationalDisplayNames;
private final Map relationalNametags;
private final Map cachedPlaceholders;
+ private final Map cachedListOrders;
private String lastDisplayName;
private Component lastHeader;
private Component lastFooter;
private String teamName;
+ @Setter
+ private int listOrder = -1;
@Nullable
@Setter
private UpdateTeamsPacket.TeamColor teamColor;
@@ -86,6 +92,7 @@ public final class TabPlayer implements Comparable {
this.relationalDisplayNames = Maps.newConcurrentMap();
this.relationalNametags = Maps.newConcurrentMap();
this.cachedPlaceholders = Maps.newConcurrentMap();
+ this.cachedListOrders = Maps.newConcurrentMap();
}
@NotNull
@@ -237,6 +244,10 @@ public final class TabPlayer implements Comparable {
relationalNametags.remove(target);
}
+ public void unsetTabListOrder(@NotNull UUID target) {
+ cachedListOrders.remove(target);
+ }
+
public Optional getRelationalNametag(@NotNull UUID target) {
return Optional.ofNullable(relationalNametags.get(target));
}
@@ -248,6 +259,8 @@ public final class TabPlayer implements Comparable {
lastHeader = null;
lastFooter = null;
role = Role.DEFAULT_ROLE;
+ teamName = null;
+ cachedListOrders.clear();
}
/**
diff --git a/src/main/java/net/william278/velocitab/providers/LoggerProvider.java b/src/main/java/net/william278/velocitab/providers/LoggerProvider.java
index 92a7662..0e1c36a 100644
--- a/src/main/java/net/william278/velocitab/providers/LoggerProvider.java
+++ b/src/main/java/net/william278/velocitab/providers/LoggerProvider.java
@@ -30,6 +30,7 @@ public interface LoggerProvider {
*
* @return the logger for the class
*/
+ @NotNull
Logger getLogger();
/**
diff --git a/src/main/java/net/william278/velocitab/providers/MetricProvider.java b/src/main/java/net/william278/velocitab/providers/MetricProvider.java
index 7436515..51b64a2 100644
--- a/src/main/java/net/william278/velocitab/providers/MetricProvider.java
+++ b/src/main/java/net/william278/velocitab/providers/MetricProvider.java
@@ -36,7 +36,8 @@ public interface MetricProvider {
/**
* Retrieves the Velocitab plugin instance.
- * @return
+ *
+ * @return The Velocitab plugin instance.
*/
Velocitab getPlugin();
diff --git a/src/main/java/net/william278/velocitab/providers/ScoreboardProvider.java b/src/main/java/net/william278/velocitab/providers/ScoreboardProvider.java
index 81733fd..5c0b9eb 100644
--- a/src/main/java/net/william278/velocitab/providers/ScoreboardProvider.java
+++ b/src/main/java/net/william278/velocitab/providers/ScoreboardProvider.java
@@ -24,7 +24,6 @@ import net.william278.velocitab.packet.ScoreboardManager;
import net.william278.velocitab.sorting.SortingManager;
import net.william278.velocitab.tab.PlayerTabList;
-import java.util.Optional;
import java.util.concurrent.TimeUnit;
public interface ScoreboardProvider {
@@ -37,11 +36,10 @@ public interface ScoreboardProvider {
Velocitab getPlugin();
/**
- * Retrieves the optional scoreboard manager.
- *
- * @return An {@code Optional} object that may contain a {@code ScoreboardManager} instance.
+ * Retrieves the scoreboard manager
+ * @return The scoreboard manager
*/
- Optional getScoreboardManager();
+ ScoreboardManager getScoreboardManager();
/**
* Sets the scoreboard manager.
@@ -85,11 +83,9 @@ public interface ScoreboardProvider {
*
*/
default void prepareScoreboard() {
- if (getPlugin().getSettings().isSendScoreboardPackets()) {
- final ScoreboardManager scoreboardManager = new ScoreboardManager(getPlugin());
- setScoreboardManager(scoreboardManager);
- scoreboardManager.registerPacket();
- }
+ final ScoreboardManager scoreboardManager = new ScoreboardManager(getPlugin(), getPlugin().getSettings().isSendScoreboardPackets());
+ setScoreboardManager(scoreboardManager);
+ scoreboardManager.registerPacket();
final PlayerTabList tabList = new PlayerTabList(getPlugin());
setTabList(tabList);
@@ -105,10 +101,8 @@ public interface ScoreboardProvider {
* Disables the ScoreboardManager and closes the tab list for the player.
*/
default void disableScoreboardManager() {
- if (getScoreboardManager().isPresent() && getPlugin().getSettings().isSendScoreboardPackets()) {
- getScoreboardManager().get().close();
- getScoreboardManager().get().unregisterPacket();
- }
+ getScoreboardManager().close();
+ getScoreboardManager().unregisterPacket();
getTabList().close();
}
diff --git a/src/main/java/net/william278/velocitab/sorting/SortedSet.java b/src/main/java/net/william278/velocitab/sorting/SortedSet.java
new file mode 100644
index 0000000..fe9b8d6
--- /dev/null
+++ b/src/main/java/net/william278/velocitab/sorting/SortedSet.java
@@ -0,0 +1,74 @@
+/*
+ * This file is part of Velocitab, licensed under the Apache License 2.0.
+ *
+ * Copyright (c) William278
+ * Copyright (c) contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.william278.velocitab.sorting;
+
+import com.google.common.collect.Maps;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.concurrent.ConcurrentSkipListSet;
+
+public class SortedSet {
+
+ private final ConcurrentSkipListSet sortedTeams;
+ private final Map positionMap;
+
+ public SortedSet(@NotNull Comparator comparator) {
+ sortedTeams = new ConcurrentSkipListSet<>(comparator);
+ positionMap = Maps.newConcurrentMap();
+ }
+
+ public synchronized boolean addTeam(@NotNull String teamName) {
+ final boolean result = sortedTeams.add(teamName);
+ if (!result) {
+ return false;
+ }
+ updatePositions();
+ return true;
+ }
+
+ public synchronized boolean removeTeam(@NotNull String teamName) {
+ final boolean result = sortedTeams.remove(teamName);
+ if (!result) {
+ return false;
+ }
+ updatePositions();
+ return true;
+ }
+
+ private synchronized void updatePositions() {
+ int index = 0;
+ positionMap.clear();
+ for (final String team : sortedTeams) {
+ positionMap.put(team, index);
+ index++;
+ }
+ }
+
+ public synchronized int getPosition(@NotNull String teamName) {
+ return positionMap.getOrDefault(teamName, -1);
+ }
+
+ @Override
+ public String toString() {
+ return sortedTeams.toString();
+ }
+}
diff --git a/src/main/java/net/william278/velocitab/tab/GroupTasks.java b/src/main/java/net/william278/velocitab/tab/GroupTasks.java
index fc8879a..ce4318b 100644
--- a/src/main/java/net/william278/velocitab/tab/GroupTasks.java
+++ b/src/main/java/net/william278/velocitab/tab/GroupTasks.java
@@ -22,7 +22,8 @@ package net.william278.velocitab.tab;
import com.velocitypowered.api.scheduler.ScheduledTask;
import org.jetbrains.annotations.Nullable;
-public record GroupTasks(@Nullable ScheduledTask updateTask, @Nullable ScheduledTask headerFooterTask, @Nullable ScheduledTask latencyTask) {
+public record GroupTasks(@Nullable ScheduledTask updateTask, @Nullable ScheduledTask headerFooterTask,
+ @Nullable ScheduledTask latencyTask) {
public void cancel() {
if (updateTask != null) {
diff --git a/src/main/java/net/william278/velocitab/tab/Nametag.java b/src/main/java/net/william278/velocitab/tab/Nametag.java
index cd29b25..9fef606 100644
--- a/src/main/java/net/william278/velocitab/tab/Nametag.java
+++ b/src/main/java/net/william278/velocitab/tab/Nametag.java
@@ -50,4 +50,8 @@ public record Nametag(@NotNull String prefix, @NotNull String suffix) {
return (prefix.equals(other.prefix)) && (suffix.equals(other.suffix));
}
+ public boolean isEmpty() {
+ return prefix.isEmpty() && suffix.isEmpty();
+ }
+
}
diff --git a/src/main/java/net/william278/velocitab/tab/PlayerTabList.java b/src/main/java/net/william278/velocitab/tab/PlayerTabList.java
index 472d134..c763cb1 100644
--- a/src/main/java/net/william278/velocitab/tab/PlayerTabList.java
+++ b/src/main/java/net/william278/velocitab/tab/PlayerTabList.java
@@ -26,6 +26,8 @@ import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.player.TabList;
import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.proxy.server.RegisteredServer;
+import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
+import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfoPacket;
import lombok.AccessLevel;
import lombok.Getter;
import net.kyori.adventure.text.Component;
@@ -34,6 +36,7 @@ import net.william278.velocitab.api.PlayerAddedToTabEvent;
import net.william278.velocitab.config.Group;
import net.william278.velocitab.config.Placeholder;
import net.william278.velocitab.config.ServerUrl;
+import net.william278.velocitab.packet.ScoreboardManager;
import net.william278.velocitab.player.Role;
import net.william278.velocitab.player.TabPlayer;
import org.jetbrains.annotations.NotNull;
@@ -46,10 +49,13 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
+import static com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfoPacket.Action.UPDATE_LIST_ORDER;
+
/**
- * The main class for tracking the server TAB list
+ * The main class for tracking the server TAB list for a map of {@link TabPlayer}s
*/
public class PlayerTabList {
+
private final Velocitab plugin;
@Getter
private final VanishTabList vanishTabList;
@@ -141,6 +147,7 @@ public class PlayerTabList {
players.values().forEach(p -> {
p.unsetRelationalDisplayName(player.getUniqueId());
p.unsetRelationalNametag(player.getUniqueId());
+ p.unsetTabListOrder(player.getUniqueId());
});
}
@@ -220,10 +227,9 @@ public class PlayerTabList {
}
iteratedPlayer.sendHeaderAndFooter(this);
}
- plugin.getScoreboardManager().ifPresent(s -> {
- s.resendAllTeams(tabPlayer);
- tabPlayer.getTeamName(plugin).thenAccept(t -> s.updateRole(tabPlayer, t, false));
- });
+ final ScoreboardManager scoreboardManager = plugin.getScoreboardManager();
+ scoreboardManager.resendAllTeams(tabPlayer);
+ updateSorting(tabPlayer, false);
fixDuplicateEntries(joined);
// Fire event without listening for result
plugin.getServer().getEventManager().fireAndForget(new PlayerAddedToTabEvent(tabPlayer, group));
@@ -278,7 +284,7 @@ public class PlayerTabList {
.filter(entry -> !entry.getKey().equals(target.getUniqueId()))
.forEach(entry -> target.getTabList().removeEntry(entry.getKey()));
} catch (Throwable error) {
- plugin.log(Level.ERROR, "Failed to fix duplicate entries for class " + target.getTabList().getClass().getName() , error);
+ plugin.log(Level.ERROR, "Failed to fix duplicate entries for class " + target.getTabList().getClass().getName(), error);
}
}
@@ -288,12 +294,13 @@ public class PlayerTabList {
/**
* Remove a player from the tab list
- * @param uuid
+ *
+ * @param uuid {@link UUID} of the {@link TabPlayer player} to remove
*/
- protected void removeTablistUUID(@NotNull UUID uuid) {
- getPlayers().forEach((key, value) -> {
- value.getPlayer().getTabList().getEntry(uuid).ifPresent(entry -> value.getPlayer().getTabList().removeEntry(uuid));
- });
+ protected void removeTabListUUID(@NotNull UUID uuid) {
+ getPlayers().forEach((key, value) -> value.getPlayer().getTabList().getEntry(uuid).ifPresent(
+ entry -> value.getPlayer().getTabList().removeEntry(uuid)
+ ));
}
protected void removePlayer(@NotNull Player target, @Nullable RegisteredServer server) {
@@ -318,7 +325,7 @@ public class PlayerTabList {
.delay(250, TimeUnit.MILLISECONDS)
.schedule();
// Delete player team
- plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(target));
+ plugin.getScoreboardManager().resetCache(target);
//remove player from tab list cache
getPlayers().remove(uuid);
}
@@ -389,13 +396,25 @@ public class PlayerTabList {
return;
}
+ updateSorting(tabPlayer, force);
+ }
+
+ private void updateSorting(@NotNull TabPlayer tabPlayer, boolean force) {
tabPlayer.getTeamName(plugin).thenAccept(teamName -> {
if (teamName.isBlank()) {
return;
}
- plugin.getScoreboardManager().ifPresent(manager -> manager.updateRole(
- tabPlayer, teamName, force
- ));
+ plugin.getScoreboardManager().updateRole(tabPlayer, teamName, force).thenAccept(v -> {
+ final int order = plugin.getScoreboardManager().getPosition(teamName);
+ if (order == -1) {
+ plugin.log(Level.ERROR, "Failed to get position for " + tabPlayer.getPlayer().getUsername());
+ return;
+ }
+
+ tabPlayer.setListOrder(order);
+ final Set players = tabPlayer.getGroup().getTabPlayers(plugin, tabPlayer);
+ players.forEach(p -> recalculateSortingForPlayer(p, players));
+ });
});
}
@@ -533,4 +552,44 @@ public class PlayerTabList {
players.remove(player.getUniqueId());
}
+ /**
+ * Whether the player can use server-side specified TAB list ordering (Minecraft 1.21.2+)
+ *
+ * @param tabPlayer player to check
+ * @return {@code true} if the user is on Minecraft 1.21.2+; {@code false}
+ */
+ private boolean hasListOrder(@NotNull TabPlayer tabPlayer) {
+ return tabPlayer.getPlayer().getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_21_2);
+ }
+
+ private void updateSorting(@NotNull TabPlayer tabPlayer, @NotNull UUID uuid, int position) {
+ if (!tabPlayer.getPlayer().getTabList().containsEntry(uuid)) {
+ return;
+ }
+ if (tabPlayer.getCachedListOrders().containsKey(uuid) && tabPlayer.getCachedListOrders().get(uuid) == position) {
+ return;
+ }
+ tabPlayer.getCachedListOrders().put(uuid, position);
+ final UpsertPlayerInfoPacket packet = new UpsertPlayerInfoPacket(UPDATE_LIST_ORDER);
+ final UpsertPlayerInfoPacket.Entry entry = new UpsertPlayerInfoPacket.Entry(uuid);
+ entry.setListOrder(position);
+ packet.addEntry(entry);
+ ((ConnectedPlayer) tabPlayer.getPlayer()).getConnection().write(packet);
+ }
+
+ private String getPlayerName(UUID uuid) {
+ return plugin.getServer().getPlayer(uuid).map(Player::getUsername).orElse("Unknown");
+ }
+
+ public synchronized void recalculateSortingForPlayer(@NotNull TabPlayer tabPlayer, @NotNull Set players) {
+ if (!hasListOrder(tabPlayer)) {
+ return;
+ }
+
+ players.forEach(p -> {
+ final int order = p.getListOrder();
+ updateSorting(tabPlayer, p.getPlayer().getUniqueId(), order);
+ });
+ }
+
}
diff --git a/src/main/java/net/william278/velocitab/tab/TabListListener.java b/src/main/java/net/william278/velocitab/tab/TabListListener.java
index ed231bc..d03848a 100644
--- a/src/main/java/net/william278/velocitab/tab/TabListListener.java
+++ b/src/main/java/net/william278/velocitab/tab/TabListListener.java
@@ -48,7 +48,8 @@ public class TabListListener {
private final Velocitab plugin;
private final PlayerTabList tabList;
- // In 1.8 there is a packet delay problem
+
+ // Set of UUIDs of users who just left the game - fixes packet delay problem on Minecraft 1.8.x
private final Set justQuit;
public TabListListener(@NotNull Velocitab plugin, @NotNull PlayerTabList tabList) {
@@ -102,7 +103,7 @@ public class TabListListener {
plugin.getTabList().clearCachedData(joined);
if (!plugin.getSettings().isShowAllPlayersFromAllGroups() && previousGroup.isPresent()
- && (groupOptional.isPresent() && !previousGroup.get().equals(groupOptional.get())
+ && (groupOptional.isPresent() && !previousGroup.get().equals(groupOptional.get())
|| groupOptional.isEmpty())) {
tabList.removeOldEntry(previousGroup.get(), joined.getUniqueId());
}
@@ -126,7 +127,7 @@ public class TabListListener {
final Component currentHeader = joined.getPlayerListHeader();
final Component currentFooter = joined.getPlayerListFooter();
if ((header.equals(currentHeader) && footer.equals(currentFooter)) ||
- (currentHeader.equals(Component.empty()) && currentFooter.equals(Component.empty()))
+ (currentHeader.equals(Component.empty()) && currentFooter.equals(Component.empty()))
) {
joined.sendPlayerListHeaderAndFooter(Component.empty(), Component.empty());
joined.getCurrentServer().ifPresent(serverConnection -> serverConnection.getServer().getPlayersConnected().forEach(player ->
@@ -144,7 +145,7 @@ public class TabListListener {
}
final Group group = groupOptional.get();
- plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(joined, group));
+ plugin.getScoreboardManager().resetCache(joined, group);
if (justQuit.contains(joined.getUniqueId())) {
plugin.getServer().getScheduler().buildTask(plugin,
() -> tabList.joinPlayer(joined, group))
@@ -156,7 +157,8 @@ public class TabListListener {
tabList.joinPlayer(joined, group);
}
- @Subscribe(order = PostOrder.LAST)
+ @SuppressWarnings("deprecation")
+ @Subscribe(order = PostOrder.CUSTOM, priority = Short.MIN_VALUE)
public void onPlayerQuit(@NotNull DisconnectEvent event) {
if (event.getLoginStatus() != DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN) {
checkDelayedDisconnect(event);
@@ -178,7 +180,7 @@ public class TabListListener {
return;
}
- tabList.removeTablistUUID(event.getPlayer().getUniqueId());
+ tabList.removeTabListUUID(event.getPlayer().getUniqueId());
}).delay(750, TimeUnit.MILLISECONDS).schedule();
}
diff --git a/src/main/java/net/william278/velocitab/tab/VanishTabList.java b/src/main/java/net/william278/velocitab/tab/VanishTabList.java
index 4f692cb..10374f2 100644
--- a/src/main/java/net/william278/velocitab/tab/VanishTabList.java
+++ b/src/main/java/net/william278/velocitab/tab/VanishTabList.java
@@ -89,21 +89,21 @@ public class VanishTabList {
final String serverName = target.getServerName();
if (tabPlayer.getGroup().onlyListPlayersInSameServer()
- && !tabPlayer.getServerName().equals(serverName)) {
+ && !tabPlayer.getServerName().equals(serverName)) {
return;
}
final boolean canSee = !plugin.getVanishManager().isVanished(p.getUsername()) ||
- plugin.getVanishManager().canSee(player.getUsername(), p.getUsername());
+ plugin.getVanishManager().canSee(player.getUsername(), p.getUsername());
if (!canSee) {
player.getTabList().removeEntry(p.getUniqueId());
- plugin.getScoreboardManager().ifPresent(s -> s.recalculateVanishForPlayer(tabPlayer, target, false));
+ plugin.getScoreboardManager().recalculateVanishForPlayer(tabPlayer, target, false);
} else {
if (!player.getTabList().containsEntry(p.getUniqueId())) {
final TabListEntry tabListEntry = tabList.createEntry(target, player.getTabList(), tabPlayer);
player.getTabList().addEntry(tabListEntry);
- plugin.getScoreboardManager().ifPresent(s -> s.recalculateVanishForPlayer(tabPlayer, target, true));
+ plugin.getScoreboardManager().recalculateVanishForPlayer(tabPlayer, target, true);
}
}
});
diff --git a/src/main/java/net/william278/velocitab/util/QuadFunction.java b/src/main/java/net/william278/velocitab/util/QuadFunction.java
index b7c07b3..285bae4 100644
--- a/src/main/java/net/william278/velocitab/util/QuadFunction.java
+++ b/src/main/java/net/william278/velocitab/util/QuadFunction.java
@@ -32,4 +32,5 @@ public interface QuadFunction {
* @return the function result
*/
R apply(T t, U u, V v, S s);
+
}
\ No newline at end of file
diff --git a/src/main/java/net/william278/velocitab/util/SerializerUtil.java b/src/main/java/net/william278/velocitab/util/SerializationUtil.java
similarity index 96%
rename from src/main/java/net/william278/velocitab/util/SerializerUtil.java
rename to src/main/java/net/william278/velocitab/util/SerializationUtil.java
index 6f863b7..4c0ed73 100644
--- a/src/main/java/net/william278/velocitab/util/SerializerUtil.java
+++ b/src/main/java/net/william278/velocitab/util/SerializationUtil.java
@@ -21,7 +21,7 @@ package net.william278.velocitab.util;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
-public final class SerializerUtil {
+public final class SerializationUtil {
public final static LegacyComponentSerializer LEGACY_SERIALIZER = LegacyComponentSerializer.builder()
.hexCharacter('#')
diff --git a/src/main/java/net/william278/velocitab/vanish/VanishManager.java b/src/main/java/net/william278/velocitab/vanish/VanishManager.java
index 607b36d..e2d73dd 100644
--- a/src/main/java/net/william278/velocitab/vanish/VanishManager.java
+++ b/src/main/java/net/william278/velocitab/vanish/VanishManager.java
@@ -61,7 +61,7 @@ public class VanishManager {
}
plugin.getTabList().getVanishTabList().vanishPlayer(tabPlayer.get());
- plugin.getScoreboardManager().ifPresent(scoreboardManager -> scoreboardManager.vanishPlayer(tabPlayer.get()));
+ plugin.getScoreboardManager().vanishPlayer(tabPlayer.get());
}
public void unVanishPlayer(@NotNull Player player) {
@@ -72,6 +72,6 @@ public class VanishManager {
}
plugin.getTabList().getVanishTabList().unVanishPlayer(tabPlayer.get());
- plugin.getScoreboardManager().ifPresent(scoreboardManager -> scoreboardManager.unVanishPlayer(tabPlayer.get()));
+ plugin.getScoreboardManager().unVanishPlayer(tabPlayer.get());
}
}
diff --git a/src/main/resources/metadata.yml b/src/main/resources/metadata.yml
index 9fa88f5..b1aafc2 100644
--- a/src/main/resources/metadata.yml
+++ b/src/main/resources/metadata.yml
@@ -1,2 +1,3 @@
velocity_api_version: '${velocity_api_version}'
-velocity_minimum_build: ${velocity_minimum_build}
\ No newline at end of file
+velocity_minimum_build: ${velocity_minimum_build}
+papi_proxy_bridge_minimum_version: '${papi_proxy_bridge_minimum_version}'
\ No newline at end of file