Fix for various problems (#119)

* Fixed logic problems with vanish + added tab recalculate system when luckperms fires UserDataRecalculateEvent

* Fix for https://github.com/WiIIiam278/Velocitab/issues/120 .
Fix for rgb nametags with legacy formatter.
Fix for players with escape characters in their name.
Fix for when a player is kicked from a server while staying online, tablist wasn't updated for that player.
Fix for vanish, wrong variable used.
Fix for negative values as input for tab sorting, min value is now 0.
This commit is contained in:
AlexDev_ 2023-11-14 17:58:33 +01:00 committed by GitHub
parent e4df93ca3f
commit 2becf43845
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 210 additions and 49 deletions

View File

@ -77,7 +77,12 @@ public enum Placeholder {
}
return plugin.getPAPIProxyBridgeHook()
.map(hook -> hook.formatPlaceholders(replaced, player.getPlayer()))
.map(hook -> hook.formatPlaceholders(replaced, player.getPlayer())
.exceptionally(e -> {
plugin.log(Level.ERROR, "An error occurred whilst parsing placeholders: " + e.getMessage());
return replaced;
})
)
.orElse(CompletableFuture.completedFuture(replaced)).exceptionally(e -> {
plugin.log(Level.ERROR, "An error occurred whilst parsing placeholders: " + e.getMessage());
return replaced;

View File

@ -89,9 +89,17 @@ public class LuckPermsHook extends Hook {
plugin.getServer().getPlayer(event.getUser().getUniqueId())
.ifPresent(player -> plugin.getServer().getScheduler()
.buildTask(plugin, () -> {
final TabPlayer tabPlayer = tabList.getTabPlayer(player).orElseThrow();
final Optional<TabPlayer> tabPlayerOptional = tabList.getTabPlayer(player);
if (tabPlayerOptional.isEmpty()) {
return;
}
final TabPlayer tabPlayer = tabPlayerOptional.get();
final Role oldRole = tabPlayer.getRole();
tabPlayer.setRole(getRoleFromMetadata(event.getData().getMetaData()));
tabList.updatePlayerDisplayName(tabPlayer);
tabList.recalculateVanishForPlayer(tabPlayer);
checkRoleUpdate(tabPlayer, oldRole);
})
.delay(500, TimeUnit.MILLISECONDS)
.schedule());
@ -114,5 +122,12 @@ public class LuckPermsHook extends Hook {
return api.getUserManager().getUser(uuid);
}
private void checkRoleUpdate(@NotNull TabPlayer player, @NotNull Role oldRole) {
if (oldRole.equals(player.getRole())) {
return;
}
plugin.getTabList().updatePlayer(player);
}
}

View File

@ -23,6 +23,7 @@ package net.william278.velocitab.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.william278.velocitab.Velocitab;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@ -35,8 +36,8 @@ import java.util.Set;
@SuppressWarnings("DuplicatedCode")
public class Protocol340Adapter extends TeamsPacketAdapter {
public Protocol340Adapter() {
super(Set.of(ProtocolVersion.MINECRAFT_1_12_2));
public Protocol340Adapter(@NotNull Velocitab plugin) {
super(plugin, Set.of(ProtocolVersion.MINECRAFT_1_12_2));
}
@Override

View File

@ -23,6 +23,11 @@ package net.william278.velocitab.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.william278.velocitab.Velocitab;
import net.william278.velocitab.config.Formatter;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@ -35,8 +40,8 @@ import java.util.Set;
@SuppressWarnings("DuplicatedCode")
public class Protocol403Adapter extends TeamsPacketAdapter {
public Protocol403Adapter() {
super(Set.of(ProtocolVersion.MINECRAFT_1_13_2,
public Protocol403Adapter(@NotNull Velocitab plugin) {
super(plugin, Set.of(ProtocolVersion.MINECRAFT_1_13_2,
ProtocolVersion.MINECRAFT_1_14,
ProtocolVersion.MINECRAFT_1_14_1,
ProtocolVersion.MINECRAFT_1_14_2,
@ -79,8 +84,8 @@ public class Protocol403Adapter extends TeamsPacketAdapter {
ProtocolUtils.writeString(byteBuf, packet.nameTagVisibility().id());
ProtocolUtils.writeString(byteBuf, packet.collisionRule().id());
byteBuf.writeByte(packet.color());
ProtocolUtils.writeString(byteBuf, getChatString(packet.prefix()));
ProtocolUtils.writeString(byteBuf, getChatString(packet.suffix()));
ProtocolUtils.writeString(byteBuf, getRGBChat(packet.prefix()));
ProtocolUtils.writeString(byteBuf, getRGBChat(packet.suffix()));
}
if (mode == UpdateTeamsPacket.UpdateMode.CREATE_TEAM || mode == UpdateTeamsPacket.UpdateMode.ADD_PLAYERS || mode == UpdateTeamsPacket.UpdateMode.REMOVE_PLAYERS) {
List<String> entities = packet.entities();
@ -90,4 +95,15 @@ public class Protocol403Adapter extends TeamsPacketAdapter {
}
}
}
@NotNull
private String getRGBChat(@NotNull String input) {
if (!getPlugin().getFormatter().equals(Formatter.LEGACY)) {
return getChatString(input);
}
final Component component = LegacyComponentSerializer.builder().hexColors().character('&').build().deserialize(input);
return GsonComponentSerializer.gson().serialize(component);
}
}

View File

@ -23,6 +23,7 @@ package net.william278.velocitab.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.william278.velocitab.Velocitab;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@ -35,8 +36,8 @@ import java.util.Set;
@SuppressWarnings("DuplicatedCode")
public class Protocol48Adapter extends TeamsPacketAdapter {
public Protocol48Adapter() {
super(Set.of(ProtocolVersion.MINECRAFT_1_8));
public Protocol48Adapter(@NotNull Velocitab plugin) {
super(plugin, Set.of(ProtocolVersion.MINECRAFT_1_8));
}
@Override

View File

@ -33,6 +33,7 @@ import org.slf4j.event.Level;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import static com.velocitypowered.api.network.ProtocolVersion.*;
@ -43,20 +44,20 @@ public class ScoreboardManager {
private final Velocitab plugin;
private final Set<TeamsPacketAdapter> versions;
private final Map<UUID, String> createdTeams;
private final Map<String, String> nametags;
private final Map<String, String> nameTags;
public ScoreboardManager(@NotNull Velocitab velocitab) {
this.plugin = velocitab;
this.createdTeams = new ConcurrentHashMap<>();
this.nametags = new ConcurrentHashMap<>();
this.nameTags = new ConcurrentHashMap<>();
this.versions = new HashSet<>();
this.registerVersions();
}
private void registerVersions() {
versions.add(new Protocol403Adapter());
versions.add(new Protocol340Adapter());
versions.add(new Protocol48Adapter());
versions.add(new Protocol403Adapter(plugin));
versions.add(new Protocol340Adapter(plugin));
versions.add(new Protocol48Adapter(plugin));
}
@NotNull
@ -99,10 +100,9 @@ public class ScoreboardManager {
final UpdateTeamsPacket packet = UpdateTeamsPacket.removeTeam(plugin, teamName);
siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> {
final boolean canSee = !plugin.getVanishManager().isVanished(connected.getUsername())
|| plugin.getVanishManager().canSee(player.getUsername(), player.getUsername());
final boolean canSee = plugin.getVanishManager().canSee(connected.getUsername(), player.getUsername());
if (!canSee) {
if (canSee) {
return;
}
@ -128,7 +128,7 @@ public class ScoreboardManager {
return;
}
final String nametag = nametags.getOrDefault(teamName, "");
final String nametag = nameTags.getOrDefault(teamName, "");
if (nametag.isEmpty()) {
return;
}
@ -138,7 +138,7 @@ public class ScoreboardManager {
final String suffix = split.length > 1 ? split[1] : "";
final UpdateTeamsPacket packet = UpdateTeamsPacket.create(plugin, createdTeams.get(player.getUniqueId()), "", prefix, suffix, player.getUsername());
siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> dispatchPacket(packet, connected)));
siblings.forEach(server -> server.getPlayersConnected().stream().filter(p -> p != player).forEach(connected -> dispatchPacket(packet, connected)));
}
public void updateRole(@NotNull Player player, @NotNull String role) {
@ -151,7 +151,7 @@ public class ScoreboardManager {
final TabPlayer tabPlayer = plugin.getTabList().getTabPlayer(player).orElseThrow();
tabPlayer.getNametag(plugin).thenAccept(nametag -> {
final String[] split = nametag.split(player.getUsername(), 2);
final String[] split = nametag.split(Pattern.quote(player.getUsername()), 2);
final String prefix = split[0];
final String suffix = split.length > 1 ? split[1] : "";
@ -161,10 +161,10 @@ public class ScoreboardManager {
}
createdTeams.put(player.getUniqueId(), role);
this.nametags.put(role, prefix + NAMETAG_DELIMITER + suffix);
this.nameTags.put(role, prefix + NAMETAG_DELIMITER + suffix);
dispatchGroupPacket(UpdateTeamsPacket.create(plugin, role, "", prefix, suffix, name), player);
} else if (!this.nametags.getOrDefault(role, "").equals(prefix + NAMETAG_DELIMITER + suffix)) {
this.nametags.put(role, prefix + NAMETAG_DELIMITER + suffix);
} else if (!this.nameTags.getOrDefault(role, "").equals(prefix + NAMETAG_DELIMITER + suffix)) {
this.nameTags.put(role, prefix + NAMETAG_DELIMITER + suffix);
dispatchGroupPacket(UpdateTeamsPacket.changeNameTag(plugin, role, prefix, suffix), player);
}
}).exceptionally(e -> {
@ -198,8 +198,7 @@ public class ScoreboardManager {
return;
}
if (plugin.getVanishManager().isVanished(p.getUsername()) ||
!plugin.getVanishManager().canSee(player.getUsername(), p.getUsername())) {
if (!plugin.getVanishManager().canSee(player.getUsername(), p.getUsername())) {
return;
}
@ -215,7 +214,7 @@ public class ScoreboardManager {
roles.add(role);
final String nametag = nametags.getOrDefault(role, "");
final String nametag = nameTags.getOrDefault(role, "");
if (nametag.isEmpty()) {
return;
}
@ -251,8 +250,7 @@ public class ScoreboardManager {
final List<RegisteredServer> siblings = plugin.getTabList().getGroupServers(serverInfo.getServerInfo().getName());
siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> {
try {
final boolean canSee = !plugin.getVanishManager().isVanished(connected.getUsername())
|| plugin.getVanishManager().canSee(player.getUsername(), player.getUsername());
final boolean canSee = plugin.getVanishManager().canSee(connected.getUsername(), player.getUsername());
if (!canSee) {
return;
}
@ -298,5 +296,39 @@ public class ScoreboardManager {
}
}
/**
* Recalculates the vanish status for a specific player.
* This method updates the player's scoreboard to reflect the vanish status of another player.
*
* @param tabPlayer The TabPlayer object representing the player whose scoreboard will be updated.
* @param target The TabPlayer object representing the player whose vanish status will be reflected.
* @param canSee A boolean indicating whether the player can see the target player.
*/
public void recalculateVanishForPlayer(TabPlayer tabPlayer, TabPlayer target, boolean canSee) {
final Player player = tabPlayer.getPlayer();
final String team = createdTeams.get(target.getPlayer().getUniqueId());
if (team == null) {
return;
}
final UpdateTeamsPacket removeTeam = UpdateTeamsPacket.removeTeam(plugin, team);
dispatchPacket(removeTeam, player);
if (canSee) {
final String nametag = nameTags.getOrDefault(team, "");
if (nametag.isEmpty()) {
return;
}
final String[] split = nametag.split(NAMETAG_DELIMITER, 2);
final String prefix = split[0];
final String suffix = split.length > 1 ? split[1] : "";
final UpdateTeamsPacket addTeam = UpdateTeamsPacket.create(plugin, team, "", prefix, suffix, target.getPlayer().getUsername());
dispatchPacket(addTeam, player);
}
}
}

View File

@ -24,6 +24,7 @@ import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.william278.velocitab.Velocitab;
import org.apache.commons.text.StringEscapeUtils;
import org.jetbrains.annotations.NotNull;
@ -33,6 +34,7 @@ import java.util.Set;
@RequiredArgsConstructor
public abstract class TeamsPacketAdapter {
private final Velocitab plugin;
private final Set<ProtocolVersion> protocolVersions;
public abstract void encode(@NotNull ByteBuf byteBuf, @NotNull UpdateTeamsPacket packet);

View File

@ -26,6 +26,8 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import lombok.*;
import lombok.experimental.Accessors;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.william278.velocitab.Velocitab;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -127,7 +129,15 @@ public class UpdateTeamsPacket implements MinecraftPacket {
if (text == null) {
return 15;
}
int lastFormatIndex = text.lastIndexOf("§");
//add 1 random char at the end to make sure the last color is always found
text = text + "z";
//serialize & deserialize to downsample rgb to legacy
Component legacyComponent = LegacyComponentSerializer.legacyAmpersand().deserialize(text);
text = LegacyComponentSerializer.legacyAmpersand().serialize(legacyComponent);
int lastFormatIndex = text.lastIndexOf("&");
if (lastFormatIndex == -1 || lastFormatIndex == text.length() - 1) {
return 15;
}
@ -136,6 +146,7 @@ public class UpdateTeamsPacket implements MinecraftPacket {
return TeamColor.getColorId(last.charAt(1));
}
//Style-codes are handled as white
public enum TeamColor {
BLACK('0', 0),
DARK_BLUE('1', 1),
@ -154,10 +165,10 @@ public class UpdateTeamsPacket implements MinecraftPacket {
YELLOW('e', 14),
WHITE('f', 15),
OBFUSCATED('k', 16),
BOLD('l', 17),
STRIKETHROUGH('m', 18),
UNDERLINED('n', 19),
ITALIC('o', 20),
BOLD('f', 17),
STRIKETHROUGH('f', 18),
UNDERLINED('f', 19),
ITALIC('f', 20),
RESET('r', 21);
private final char colorChar;

View File

@ -26,6 +26,7 @@ import net.kyori.adventure.text.Component;
import net.william278.velocitab.Velocitab;
import net.william278.velocitab.config.Placeholder;
import net.william278.velocitab.tab.PlayerTabList;
import org.apache.commons.lang3.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -45,6 +46,9 @@ public final class TabPlayer implements Comparable<TabPlayer> {
private String teamName;
@Nullable
private String customName;
@Nullable
@Setter
private String lastServer;
public TabPlayer(@NotNull Player player, @NotNull Role role) {
this.player = player;
@ -76,7 +80,7 @@ public final class TabPlayer implements Comparable<TabPlayer> {
public String getServerName() {
return player.getCurrentServer()
.map(serverConnection -> serverConnection.getServerInfo().getName())
.orElse("unknown");
.orElse(ObjectUtils.firstNonNull(lastServer, "unknown"));
}
/**
@ -123,9 +127,7 @@ public final class TabPlayer implements Comparable<TabPlayer> {
@NotNull
public CompletableFuture<String> getNametag(@NotNull Velocitab plugin) {
final String serverGroup = plugin.getSettings().getServerGroup(getServerName());
return Placeholder.replace(plugin.getSettings().getNametag(serverGroup), plugin, this)
.thenApply(formatted -> plugin.getFormatter().formatLegacySymbols(formatted, this, plugin));
return Placeholder.replace(plugin.getSettings().getNametag(serverGroup), plugin, this);
}
@NotNull

View File

@ -68,8 +68,9 @@ public class SortingManager {
return "";
}
if (value.matches("[0-9]+")) {
if (value.matches("^-?[0-9]\\d*(\\.\\d+)?$")) {
double parsed = Double.parseDouble(value);
parsed = Math.max(0, parsed);
return compressNumber(Integer.MAX_VALUE / 4d - parsed);
}

View File

@ -19,8 +19,10 @@
package net.william278.velocitab.tab;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.player.KickedFromServerEvent;
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
import com.velocitypowered.api.proxy.Player;
@ -37,15 +39,13 @@ import net.william278.velocitab.config.Placeholder;
import net.william278.velocitab.player.Role;
import net.william278.velocitab.player.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.slf4j.event.Level;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.*;
/**
* The main class for tracking the server TAB list
@ -54,12 +54,14 @@ public class PlayerTabList {
private final Velocitab plugin;
private final ConcurrentHashMap<UUID, TabPlayer> players;
private final ConcurrentLinkedQueue<String> fallbackServers;
private final List<UUID> justKicked;
private ScheduledTask updateTask;
public PlayerTabList(@NotNull Velocitab plugin) {
this.plugin = plugin;
this.players = new ConcurrentHashMap<>();
this.fallbackServers = new ConcurrentLinkedQueue<>();
this.justKicked = new CopyOnWriteArrayList<>();
// If the update time is set to 0 do not schedule the updater
if (plugin.getSettings().getUpdateRate() > 0) {
@ -114,6 +116,13 @@ public class PlayerTabList {
});
}
@Subscribe
public void onKick(KickedFromServerEvent event) {
event.getPlayer().getTabList().clearAll();
event.getPlayer().getTabList().clearHeaderAndFooter();
justKicked.add(event.getPlayer().getUniqueId());
}
@SuppressWarnings("UnstableApiUsage")
@Subscribe
public void onPlayerJoin(@NotNull ServerPostConnectEvent event) {
@ -146,6 +155,16 @@ public class PlayerTabList {
final TabPlayer tabPlayer = getTabPlayer(joined).orElseGet(() -> createTabPlayer(joined));
players.putIfAbsent(joined.getUniqueId(), tabPlayer);
int delay = 500;
if (justKicked.contains(joined.getUniqueId())) {
delay = 1000;
justKicked.remove(joined.getUniqueId());
}
//store last server so it's possible to have the last server on disconnect
tabPlayer.setLastServer(joined.getCurrentServer().map(ServerConnection::getServerInfo).map(ServerInfo::getName).orElse(""));
final boolean isVanished = plugin.getVanishManager().isVanished(joined.getUsername());
// Update lists
plugin.getServer().getScheduler()
@ -164,12 +183,17 @@ public class PlayerTabList {
player.getPlayer().getTabList().removeEntry(joined.getUniqueId());
}
// check if joined player can see current player
if ((plugin.getVanishManager().isVanished(player.getPlayer().getUsername()) ||
if ((plugin.getVanishManager().isVanished(player.getPlayer().getUsername()) &&
!plugin.getVanishManager().canSee(joined.getUsername(), player.getPlayer().getUsername())) && player.getPlayer() != joined) {
tabList.removeEntry(player.getPlayer().getUniqueId());
} else {
tabList.getEntry(player.getPlayer().getUniqueId()).ifPresentOrElse(
entry -> player.getDisplayName(plugin).thenAccept(entry::setDisplayName),
entry -> player.getDisplayName(plugin).thenAccept(entry::setDisplayName)
.exceptionally(throwable -> {
plugin.log(Level.ERROR, String.format("Failed to set display name for %s (UUID: %s)",
player.getPlayer().getUsername(), player.getPlayer().getUniqueId()), throwable);
return null;
}),
() -> createEntry(player, tabList).thenAccept(tabList::addEntry)
);
}
@ -185,7 +209,7 @@ public class PlayerTabList {
// Fire event without listening for result
plugin.getServer().getEventManager().fireAndForget(new PlayerAddedToTabEvent(tabPlayer, tabPlayer.getServerGroup(plugin), serversInGroup));
})
.delay(500, TimeUnit.MILLISECONDS)
.delay(delay, TimeUnit.MILLISECONDS)
.schedule();
}
@ -224,7 +248,7 @@ public class PlayerTabList {
}
@Subscribe
@Subscribe(order = PostOrder.LAST)
public void onPlayerQuit(@NotNull DisconnectEvent event) {
if (event.getLoginStatus() != DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN) {
return;
@ -232,7 +256,8 @@ public class PlayerTabList {
// Remove the player from the tracking list, Print warning if player was not removed
final UUID uuid = event.getPlayer().getUniqueId();
if (players.remove(uuid) == null) {
final TabPlayer tabPlayer = players.get(uuid);
if (tabPlayer == null) {
plugin.log(String.format("Failed to remove disconnecting player %s (UUID: %s)",
event.getPlayer().getUsername(), uuid));
}
@ -250,7 +275,8 @@ public class PlayerTabList {
.schedule();
// Delete player team
plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(event.getPlayer()));
//remove player from tab list cache
players.remove(uuid);
}
@NotNull
@ -450,4 +476,53 @@ public class PlayerTabList {
}));
}
/**
* Recalculates the visibility of players in the tab list for the given player.
* If tabPlayer can see the player, the player will be added to the tab list.
*
* @param tabPlayer The TabPlayer object representing the player for whom to recalculate the tab list visibility.
*/
public void recalculateVanishForPlayer(@NotNull TabPlayer tabPlayer) {
final Player player = tabPlayer.getPlayer();
final Optional<List<String>> serversInGroupOptional = getGroupNames(player.getCurrentServer()
.map(ServerConnection::getServerInfo)
.map(ServerInfo::getName)
.orElse("?"));
final List<String> serversInGroup = serversInGroupOptional.orElseGet(ArrayList::new);
plugin.getServer().getAllPlayers().forEach(p -> {
if (p.equals(player)) {
return;
}
final Optional<TabPlayer> targetOptional = getTabPlayer(p);
if (targetOptional.isEmpty()) {
return;
}
final TabPlayer target = targetOptional.get();
final String serverName = target.getServerName();
if (plugin.getSettings().isOnlyListPlayersInSameGroup()
&& !serversInGroup.contains(serverName)) {
return;
}
final boolean canSee = !plugin.getVanishManager().isVanished(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));
} else {
if (!player.getTabList().containsEntry(p.getUniqueId())) {
createEntry(target, player.getTabList()).thenAccept(e -> {
player.getTabList().addEntry(e);
plugin.getScoreboardManager().ifPresent(s -> s.recalculateVanishForPlayer(tabPlayer, target, true));
});
}
}
});
}
}