From 75d9f32010452dc1a4e1d46a4c560a3e53529705 Mon Sep 17 00:00:00 2001 From: AlexDev_ <56083016+alexdev03@users.noreply.github.com> Date: Fri, 22 Sep 2023 18:57:51 +0200 Subject: [PATCH] Added support for nametags and fixed a few problems (#84) * Added regex check for placeholders to avoid useless requests. Added support for custom nametags. Due to minecraft limit only legacy chatcolor are supported. Team names now are unique, so 1 team can have max 1 player. Fixed problem with luckperms event bus while reloading the plugin. * Update src/main/java/net/william278/velocitab/config/Placeholder.java Co-authored-by: William * Update src/main/java/net/william278/velocitab/hook/LuckPermsHook.java Co-authored-by: William * Update src/main/java/net/william278/velocitab/config/Formatter.java Co-authored-by: William * Update src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java Co-authored-by: William * Fixed problem while updating display names. Changed a few method signature as requested in pr. Applied changes of pr. * Fixed problems after merging with upstream, fixed problem with player team color on join. * Update src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java Co-authored-by: William * Update src/main/java/net/william278/velocitab/packet/ScoreboardManager.java Co-authored-by: William * Update src/main/java/net/william278/velocitab/packet/ScoreboardManager.java Co-authored-by: William * Update src/main/java/net/william278/velocitab/packet/ScoreboardManager.java Co-authored-by: William * Update src/main/java/net/william278/velocitab/config/Formatter.java Co-authored-by: William * Update src/main/java/net/william278/velocitab/player/TabPlayer.java Co-authored-by: William * Fix username replacement in scoreboard and code typo This commit resolves two issues. Firstly, changed the variable that we split the nametag on in `ScoreboardManager` from a hardcoded string to the player's specific username. This rectifies an issue where incorrect splitting occurred if the username wasn't exactly "%username%". Secondly, fixed a miswritten method call in `Formatter` from '..legacySection()' to '.legacySection()', correcting a syntax error. Lastly, removed superfluous replacement in `TabPlayer's` getNametag method as it was already handled in `ScoreboardManager`. --------- Co-authored-by: William --- .../net/william278/velocitab/Velocitab.java | 13 +- .../velocitab/config/Formatter.java | 5 + .../velocitab/config/Placeholder.java | 4 + .../william278/velocitab/config/Settings.java | 15 +++ .../velocitab/hook/LuckPermsHook.java | 9 +- .../velocitab/hook/PAPIProxyBridgeHook.java | 2 +- .../velocitab/packet/ScoreboardManager.java | 124 +++++++++++++----- .../velocitab/packet/UpdateTeamsPacket.java | 83 +++++++++++- .../velocitab/player/TabPlayer.java | 29 ++-- .../velocitab/tab/PlayerTabList.java | 81 +++++++----- 10 files changed, 284 insertions(+), 81 deletions(-) diff --git a/src/main/java/net/william278/velocitab/Velocitab.java b/src/main/java/net/william278/velocitab/Velocitab.java index 4a64c7f..c052571 100644 --- a/src/main/java/net/william278/velocitab/Velocitab.java +++ b/src/main/java/net/william278/velocitab/Velocitab.java @@ -95,6 +95,7 @@ public class Velocitab { @Subscribe public void onProxyShutdown(@NotNull ProxyShutdownEvent event) { disableScoreboardManager(); + getLuckPermsHook().ifPresent(LuckPermsHook::close); logger.info("Successfully disabled Velocitab"); } @@ -119,6 +120,12 @@ public class Velocitab { new File(dataDirectory.toFile(), "config.yml"), new Settings(this) ).get(); + + settings.getNametags().values().stream() + .filter(nametag -> !nametag.contains("%username%")).forEach(nametag -> { + logger.warn("Nametag '" + nametag + "' does not contain %username% - removing"); + settings.getNametags().remove(nametag); + }); } catch (IOException | InvocationTargetException | InstantiationException | IllegalAccessException e) { logger.error("Failed to load config file: " + e.getMessage(), e); } @@ -156,7 +163,7 @@ public class Velocitab { } private void disableScoreboardManager() { - if (scoreboardManager !=null && settings.isSortPlayers()) { + if (scoreboardManager != null && settings.isSortPlayers()) { scoreboardManager.unregisterPacket(); } } @@ -184,6 +191,10 @@ public class Velocitab { ); } + public Optional getTabPlayer(String name) { + return server.getPlayer(name).map(this::getTabPlayer); + } + private void registerCommands() { final BrigadierCommand command = new VelocitabCommand(this).command(); server.getCommandManager().register( diff --git a/src/main/java/net/william278/velocitab/config/Formatter.java b/src/main/java/net/william278/velocitab/config/Formatter.java index 9ee5560..54d3a00 100644 --- a/src/main/java/net/william278/velocitab/config/Formatter.java +++ b/src/main/java/net/william278/velocitab/config/Formatter.java @@ -79,6 +79,11 @@ public enum Formatter { return formatter.apply(text, player, plugin); } + @NotNull + public String formatLegacySymbols(@NotNull String text, @NotNull TabPlayer player, @NotNull Velocitab plugin) { + return LegacyComponentSerializer.legacySection() + .serialize(format(text, player, plugin)); + } @NotNull public String escape(@NotNull String text) { return escaper.apply(text); diff --git a/src/main/java/net/william278/velocitab/config/Placeholder.java b/src/main/java/net/william278/velocitab/config/Placeholder.java index f2aff5a..eaefc28 100644 --- a/src/main/java/net/william278/velocitab/config/Placeholder.java +++ b/src/main/java/net/william278/velocitab/config/Placeholder.java @@ -65,6 +65,10 @@ public enum Placeholder { } final String replaced = format; + if (!replaced.matches("%.*?%")) { + return CompletableFuture.completedFuture(replaced); + } + return plugin.getPAPIProxyBridgeHook() .map(hook -> hook.formatPlaceholders(replaced, player.getPlayer())) .orElse(CompletableFuture.completedFuture(replaced)); diff --git a/src/main/java/net/william278/velocitab/config/Settings.java b/src/main/java/net/william278/velocitab/config/Settings.java index 86f1e25..ce70afd 100644 --- a/src/main/java/net/william278/velocitab/config/Settings.java +++ b/src/main/java/net/william278/velocitab/config/Settings.java @@ -60,6 +60,11 @@ public class Settings { @YamlKey("formats") private Map formats = Map.of("default", "&7[%server%] &f%prefix%%username%"); + @Getter + @YamlKey("nametags") + @YamlComment("Nametag(s) to display above players' heads for each server group. To disable, set to empty") + private Map nametags = Map.of("default", "&f%prefix%%username%&f%suffix%"); + @Getter @YamlComment("Which text formatter to use (MINEDOWN, MINIMESSAGE, or LEGACY)") @YamlKey("formatting_type") @@ -165,6 +170,16 @@ public class Settings { formats.getOrDefault(serverGroup, "%username%")); } + @NotNull + public String getNametag(@NotNull String serverGroup) { + return StringEscapeUtils.unescapeJava( + nametags.getOrDefault(serverGroup, "")); + } + + public boolean areNametagsEnabled() { + return !nametags.isEmpty(); + } + /** * Get display name for the server * diff --git a/src/main/java/net/william278/velocitab/hook/LuckPermsHook.java b/src/main/java/net/william278/velocitab/hook/LuckPermsHook.java index c8721c8..a434472 100644 --- a/src/main/java/net/william278/velocitab/hook/LuckPermsHook.java +++ b/src/main/java/net/william278/velocitab/hook/LuckPermsHook.java @@ -23,6 +23,7 @@ import com.velocitypowered.api.proxy.Player; import net.luckperms.api.LuckPerms; import net.luckperms.api.LuckPermsProvider; import net.luckperms.api.cacheddata.CachedMetaData; +import net.luckperms.api.event.EventSubscription; import net.luckperms.api.event.user.UserDataRecalculateEvent; import net.luckperms.api.model.group.Group; import net.luckperms.api.model.user.User; @@ -42,11 +43,16 @@ public class LuckPermsHook extends Hook { private int highestWeight = Role.DEFAULT_WEIGHT; private final LuckPerms api; + private final EventSubscription event; public LuckPermsHook(@NotNull Velocitab plugin) throws IllegalStateException { super(plugin); this.api = LuckPermsProvider.get(); - api.getEventBus().subscribe(plugin, UserDataRecalculateEvent.class, this::onLuckPermsGroupUpdate); + event = api.getEventBus().subscribe(plugin, UserDataRecalculateEvent.class, this::onLuckPermsGroupUpdate); + } + + public void close() { + event.close(); } @NotNull @@ -81,6 +87,7 @@ public class LuckPermsHook extends Hook { ); tabList.replacePlayer(updatedPlayer); tabList.updatePlayer(updatedPlayer); + tabList.updatePlayerDisplayName(updatedPlayer); }) .delay(500, TimeUnit.MILLISECONDS) .schedule()); diff --git a/src/main/java/net/william278/velocitab/hook/PAPIProxyBridgeHook.java b/src/main/java/net/william278/velocitab/hook/PAPIProxyBridgeHook.java index 709ce53..0fb86c1 100644 --- a/src/main/java/net/william278/velocitab/hook/PAPIProxyBridgeHook.java +++ b/src/main/java/net/william278/velocitab/hook/PAPIProxyBridgeHook.java @@ -32,7 +32,7 @@ public class PAPIProxyBridgeHook extends Hook { public PAPIProxyBridgeHook(@NotNull Velocitab plugin) { super(plugin); - this.api = PlaceholderAPI.getInstance(); + this.api = PlaceholderAPI.createInstance(); this.api.setCacheExpiry(Math.max(0, plugin.getSettings().getPapiCacheTime())); } diff --git a/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java b/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java index cc0ae2c..de45be4 100644 --- a/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java +++ b/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java @@ -21,15 +21,18 @@ package net.william278.velocitab.packet; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ServerConnection; +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 net.william278.velocitab.Velocitab; +import net.william278.velocitab.player.TabPlayer; import org.jetbrains.annotations.NotNull; import org.slf4j.event.Level; import java.util.*; -import java.util.stream.Collectors; +import java.util.concurrent.ConcurrentHashMap; import static com.velocitypowered.api.network.ProtocolVersion.*; @@ -37,14 +40,14 @@ public class ScoreboardManager { private PacketRegistration packetRegistration; private final Velocitab plugin; - private final Map> createdTeams; - private final Map> roleMappings; private final Set versions; + private final Map createdTeams; + private final Map nametags; public ScoreboardManager(@NotNull Velocitab velocitab) { this.plugin = velocitab; - this.createdTeams = new HashMap<>(); - this.roleMappings = new HashMap<>(); + this.createdTeams = new ConcurrentHashMap<>(); + this.nametags = new ConcurrentHashMap<>(); this.versions = new HashSet<>(); this.registerVersions(); } @@ -64,44 +67,78 @@ public class ScoreboardManager { } public void resetCache(@NotNull Player player) { - createdTeams.remove(player.getUniqueId()); - roleMappings.remove(player.getUniqueId()); + String team = createdTeams.remove(player.getUniqueId()); + if (team != null) { + dispatchGroupPacket(UpdateTeamsPacket.removeTeam(plugin, team), player); + } } - public void setRoles(@NotNull Player player, @NotNull Map playerRoles) { + public void updateRole(@NotNull Player player, @NotNull String role) { if (!player.isActive()) { plugin.getTabList().removeOfflinePlayer(player); return; } - playerRoles.entrySet().stream() - .collect(Collectors.groupingBy( - Map.Entry::getValue, - Collectors.mapping(Map.Entry::getKey, Collectors.toList()) - )) - .forEach((role, players) -> updateRoles(player, role, players.toArray(new String[0]))); + + final String name = player.getUsername(); + final TabPlayer tabPlayer = plugin.getTabPlayer(player); + tabPlayer.getNametag(plugin).thenAccept(nametag -> { + String[] split = nametag.split(player.getUsername(), 2); + String prefix = split[0]; + String suffix = split.length > 1 ? split[1] : ""; + + if (!createdTeams.getOrDefault(player.getUniqueId(), "").equals(role)) { + createdTeams.computeIfAbsent(player.getUniqueId(), k -> role); + this.nametags.put(role, prefix + ":::" + suffix); + dispatchGroupPacket(UpdateTeamsPacket.create(plugin, role, "", prefix, suffix, name), player); + } else if (!this.nametags.getOrDefault(role, "").equals(prefix + ":::" + suffix)) { + this.nametags.put(role, prefix + ":::" + suffix); + dispatchGroupPacket(UpdateTeamsPacket.changeNameTag(plugin, role, prefix, suffix), player); + } + }).exceptionally(e -> { + plugin.log(Level.ERROR, "Failed to update role for " + player.getUsername(), e); + return null; + }); } - public void updateRoles(@NotNull Player player, @NotNull String role, @NotNull String... playerNames) { - if (!player.isActive()) { - plugin.getTabList().removeOfflinePlayer(player); + + public void resendAllNameTags(Player player) { + + if(!plugin.getSettings().areNametagsEnabled()) { return; } - if (!createdTeams.getOrDefault(player.getUniqueId(), List.of()).contains(role)) { - dispatchPacket(UpdateTeamsPacket.create(plugin, role, playerNames), player); - createdTeams.computeIfAbsent(player.getUniqueId(), k -> new ArrayList<>()).add(role); - roleMappings.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>()).put(player.getUsername(), role); - } else { - roleMappings.getOrDefault(player.getUniqueId(), Map.of()) - .entrySet().stream() - .filter((entry) -> List.of(playerNames).contains(entry.getKey())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) - .forEach((playerName, oldRole) -> dispatchPacket( - UpdateTeamsPacket.removeFromTeam(plugin, oldRole, playerName), - player - )); - dispatchPacket(UpdateTeamsPacket.addToTeam(plugin, role, playerNames), player); - roleMappings.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>()).put(player.getUsername(), role); + + final Optional optionalServerConnection = player.getCurrentServer(); + if (optionalServerConnection.isEmpty()) { + return; } + + RegisteredServer serverInfo = optionalServerConnection.get().getServer(); + + List siblings = plugin.getTabList().getGroupServers(serverInfo.getServerInfo().getName()); + + List players = siblings.stream().map(RegisteredServer::getPlayersConnected).flatMap(Collection::stream).toList(); + + players.forEach(p -> { + if (p == player || !p.isActive()) { + return; + } + + final String role = createdTeams.getOrDefault(p.getUniqueId(), ""); + if (role.isEmpty()) { + return; + } + + final String nametag = nametags.getOrDefault(role, ""); + if (nametag.isEmpty()) { + return; + } + + String[] split = nametag.split(":::", 2); + String prefix = split[0]; + String suffix = split.length > 1 ? split[1] : ""; + + dispatchPacket(UpdateTeamsPacket.create(plugin, role, "", prefix, suffix, p.getUsername()), player); + }); } private void dispatchPacket(@NotNull UpdateTeamsPacket packet, @NotNull Player player) { @@ -118,6 +155,29 @@ public class ScoreboardManager { } } + private void dispatchGroupPacket(@NotNull UpdateTeamsPacket packet, @NotNull Player player) { + Optional optionalServerConnection = player.getCurrentServer(); + + if (optionalServerConnection.isEmpty()) { + return; + } + + RegisteredServer serverInfo = optionalServerConnection.get().getServer(); + + List siblings = plugin.getTabList().getGroupServers(serverInfo.getServerInfo().getName()); + + siblings.forEach(s -> { + s.getPlayersConnected().forEach(p -> { + try { + final ConnectedPlayer connectedPlayer = (ConnectedPlayer) p; + connectedPlayer.getConnection().write(packet); + } catch (Exception e) { + plugin.log(Level.ERROR, "Failed to dispatch packet (is the client or server modded or using an illegal version?)", e); + } + }); + }); + } + public void registerPacket() { try { packetRegistration = PacketRegistration.of(UpdateTeamsPacket.class) diff --git a/src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java b/src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java index 1db675b..f801b44 100644 --- a/src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java +++ b/src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java @@ -30,6 +30,7 @@ import net.william278.velocitab.Velocitab; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -61,18 +62,32 @@ public class UpdateTeamsPacket implements MinecraftPacket { } @NotNull - protected static UpdateTeamsPacket create(@NotNull Velocitab plugin, @NotNull String teamName, @NotNull String... teamMembers) { + protected static UpdateTeamsPacket create(@NotNull Velocitab plugin, @NotNull String teamName, @NotNull String displayName, @Nullable String prefix, @Nullable String suffix, @NotNull String... teamMembers) { return new UpdateTeamsPacket(plugin) .teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName) .mode(UpdateMode.CREATE_TEAM) + .displayName(displayName) + .friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY)) + .nameTagVisibility(NameTagVisibility.ALWAYS) + .collisionRule(CollisionRule.ALWAYS) + .color(getLastColor(prefix)) + .prefix(prefix == null ? "" : prefix) + .suffix(suffix == null ? "" : suffix) + .entities(Arrays.asList(teamMembers)); + } + + @NotNull + protected static UpdateTeamsPacket changeNameTag(@NotNull Velocitab plugin, @NotNull String teamName, @Nullable String prefix, @Nullable String suffix) { + return new UpdateTeamsPacket(plugin) + .teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName) + .mode(UpdateMode.UPDATE_INFO) .displayName(teamName) .friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY)) .nameTagVisibility(NameTagVisibility.ALWAYS) .collisionRule(CollisionRule.ALWAYS) - .color(15) - .prefix("") - .suffix("") - .entities(Arrays.asList(teamMembers)); + .color(getLastColor(prefix)) + .prefix(prefix == null ? "" : prefix) + .suffix(suffix == null ? "" : suffix); } @NotNull @@ -91,6 +106,64 @@ public class UpdateTeamsPacket implements MinecraftPacket { .entities(Arrays.asList(teamMembers)); } + @NotNull + protected static UpdateTeamsPacket removeTeam(@NotNull Velocitab plugin, @NotNull String teamName) { + return new UpdateTeamsPacket(plugin) + .teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName) + .mode(UpdateMode.REMOVE_TEAM); + } + + public static int getLastColor(@Nullable String text) { + if (text == null) { + return 15; + } + int intvar = text.lastIndexOf("ยง"); + + if (intvar == -1 || intvar == text.length() - 1) { + return 15; + } + + String last = text.substring(intvar, intvar + 2); + return TeamColor.getColorId(last.charAt(1)); + } + + public enum TeamColor { + BLACK('0', 0), + DARK_BLUE('1', 1), + DARK_GREEN('2', 2), + DARK_AQUA('3', 3), + DARK_RED('4', 4), + DARK_PURPLE('5', 5), + GOLD('6', 6), + GRAY('7', 7), + DARK_GRAY('8', 8), + BLUE('9', 9), + GREEN('a', 10), + AQUA('b', 11), + RED('c', 12), + LIGHT_PURPLE('d', 13), + YELLOW('e', 14), + WHITE('f', 15), + OBFUSCATED('k', 16), + BOLD('l', 17), + STRIKETHROUGH('m', 18), + UNDERLINED('n', 19), + ITALIC('o', 20), + RESET('r', 21); + + private final char colorChar; + private final int id; + + TeamColor(char colorChar, int id) { + this.colorChar= colorChar; + this.id = id; + } + + public static int getColorId(char var) { + return Arrays.stream(values()).filter(color -> color.colorChar == var).map(c -> c.id).findFirst().orElse(15); + } + } + @Override public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { throw new UnsupportedOperationException("Operation not supported"); diff --git a/src/main/java/net/william278/velocitab/player/TabPlayer.java b/src/main/java/net/william278/velocitab/player/TabPlayer.java index 1ca107b..099e455 100644 --- a/src/main/java/net/william278/velocitab/player/TabPlayer.java +++ b/src/main/java/net/william278/velocitab/player/TabPlayer.java @@ -20,6 +20,7 @@ package net.william278.velocitab.player; import com.velocitypowered.api.proxy.Player; +import lombok.Getter; import net.kyori.adventure.text.Component; import net.william278.velocitab.Velocitab; import net.william278.velocitab.config.Placeholder; @@ -36,8 +37,12 @@ public final class TabPlayer implements Comparable { private final Player player; private final Role role; private final int highestWeight; + @Getter private int headerIndex = 0; + @Getter private int footerIndex = 0; + @Getter + private Component lastDisplayname; public TabPlayer(@NotNull Player player, @NotNull Role role, int highestWeight) { this.player = player; @@ -105,7 +110,15 @@ public final class TabPlayer implements Comparable { public CompletableFuture getDisplayName(@NotNull Velocitab plugin) { final String serverGroup = plugin.getSettings().getServerGroup(getServerName()); return Placeholder.replace(plugin.getSettings().getFormat(serverGroup), plugin, this) - .thenApply(formatted -> plugin.getFormatter().format(formatted, this, plugin)); + .thenApply(formatted -> plugin.getFormatter().format(formatted, this, plugin)) + .thenApply(c -> this.lastDisplayname = c); + } + + @NotNull + public CompletableFuture 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)); } @@ -113,18 +126,16 @@ public final class TabPlayer implements Comparable { public String getTeamName(@NotNull Velocitab plugin) { return plugin.getSettings().getSortingElementList().stream() .map(element -> element.resolve(this, plugin)) - .collect(Collectors.joining("-")); + .collect(Collectors.joining("-")) + + getPlayer().getUniqueId().toString().substring(0, 3); } + public void sendHeaderAndFooter(@NotNull PlayerTabList tabList) { tabList.getHeader(this).thenAccept(header -> tabList.getFooter(this) .thenAccept(footer -> player.sendPlayerListHeaderAndFooter(header, footer))); } - public int getHeaderIndex() { - return headerIndex; - } - public void incrementHeaderIndex(@NotNull Velocitab plugin) { headerIndex++; if (headerIndex >= plugin.getSettings().getHeaderListSize(getServerGroup(plugin))) { @@ -132,10 +143,6 @@ public final class TabPlayer implements Comparable { } } - public int getFooterIndex() { - return footerIndex; - } - public void incrementFooterIndex(@NotNull Velocitab plugin) { footerIndex++; if (footerIndex >= plugin.getSettings().getFooterListSize(getServerGroup(plugin))) { @@ -174,7 +181,7 @@ public final class TabPlayer implements Comparable { ? String.format("%0" + Integer.toString(orderSize).length() + "d", position) : String.valueOf(orderSize); }), - SERVER_GROUP_NAME((player, plugin) -> player.getServerGroup(plugin)); + SERVER_GROUP_NAME(TabPlayer::getServerGroup); private final BiFunction elementResolver; diff --git a/src/main/java/net/william278/velocitab/tab/PlayerTabList.java b/src/main/java/net/william278/velocitab/tab/PlayerTabList.java index a555e0c..8aac150 100644 --- a/src/main/java/net/william278/velocitab/tab/PlayerTabList.java +++ b/src/main/java/net/william278/velocitab/tab/PlayerTabList.java @@ -36,9 +36,7 @@ import net.william278.velocitab.config.Placeholder; import net.william278.velocitab.player.TabPlayer; import org.jetbrains.annotations.NotNull; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; @@ -67,7 +65,6 @@ public class PlayerTabList { final Player joined = event.getPlayer(); plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(joined)); - // Remove the player from the tracking list if they are switching servers final RegisteredServer previousServer = event.getPreviousServer(); if (previousServer == null) { @@ -76,7 +73,7 @@ public class PlayerTabList { // Get the servers in the group from the joined server name // If the server is not in a group, use fallback - Optional> serversInGroup = getSiblings(joined.getCurrentServer() + Optional> serversInGroup = getGroupNames(joined.getCurrentServer() .map(ServerConnection::getServerInfo) .map(ServerInfo::getName) .orElse("?")); @@ -91,22 +88,17 @@ public class PlayerTabList { final TabPlayer tabPlayer = plugin.getTabPlayer(joined); players.add(tabPlayer); - - // Update lists plugin.getServer().getScheduler() .buildTask(plugin, () -> { final TabList tabList = joined.getTabList(); - final Map playerRoles = new HashMap<>(); - for (TabPlayer player : players) { // Skip players on other servers if the setting is enabled if (plugin.getSettings().isOnlyListPlayersInSameGroup() && serversInGroup.isPresent() - && !serversInGroup.get().contains(player.getServerName())) { + && !serversInGroup.get().contains(player.getServerName())) { continue; } - playerRoles.put(player.getPlayer().getUsername(), player.getTeamName(plugin)); tabList.getEntries().stream() .filter(e -> e.getProfile().getId().equals(player.getPlayer().getUniqueId())).findFirst() .ifPresentOrElse( @@ -114,13 +106,13 @@ public class PlayerTabList { () -> createEntry(player, tabList).thenAccept(tabList::addEntry) ); addPlayerToTabList(player, tabPlayer); - player.sendHeaderAndFooter(this); - } - - plugin.getScoreboardManager().ifPresent(manager -> manager.setRoles(joined, playerRoles)); + plugin.getScoreboardManager().ifPresent(s -> { + s.resendAllNameTags(joined); + s.updateRole(joined, plugin.getTabPlayer(joined).getTeamName(plugin)); + }); }) .delay(500, TimeUnit.MILLISECONDS) .schedule(); @@ -149,11 +141,6 @@ public class PlayerTabList { () -> createEntry(newPlayer, player.getPlayer().getTabList()) .thenAccept(entry -> player.getPlayer().getTabList().addEntry(entry)) ); - plugin.getScoreboardManager().ifPresent(manager -> manager.updateRoles( - player.getPlayer(), - newPlayer.getTeamName(plugin), - newPlayer.getPlayer().getUsername() - )); } @@ -177,6 +164,9 @@ public class PlayerTabList { })) .delay(500, TimeUnit.MILLISECONDS) .schedule(); + // Delete player team + plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(event.getPlayer())); + } // Replace a player in the tab list @@ -192,16 +182,27 @@ public class PlayerTabList { return; } - players.forEach(player -> tabPlayer.getDisplayName(plugin).thenAccept(displayName -> { - player.getPlayer().getTabList().getEntries().stream() - .filter(e -> e.getProfile().getId().equals(tabPlayer.getPlayer().getUniqueId())).findFirst() - .ifPresent(entry -> entry.setDisplayName(displayName)); - plugin.getScoreboardManager().ifPresent(manager -> manager.updateRoles( - player.getPlayer(), - tabPlayer.getTeamName(plugin), - tabPlayer.getPlayer().getUsername() - )); - })); + plugin.getScoreboardManager().ifPresent(manager -> manager.updateRole( + tabPlayer.getPlayer(), + tabPlayer.getTeamName(plugin) + )); + } + + public void updatePlayerDisplayName(TabPlayer tabPlayer) { + Component lastDisplayName = tabPlayer.getLastDisplayname(); + tabPlayer.getDisplayName(plugin).thenAccept(displayName -> { + if (displayName == null || displayName.equals(lastDisplayName)) return; + + players.forEach(player -> + player.getPlayer().getTabList().getEntries().stream() + .filter(e -> e.getProfile().getId().equals(tabPlayer.getPlayer().getUniqueId())).findFirst() + .ifPresent(entry -> entry.setDisplayName(displayName))); + }); + + } + + public void updateDisplayNames() { + players.forEach(this::updatePlayerDisplayName); } public CompletableFuture getHeader(@NotNull TabPlayer player) { @@ -231,6 +232,7 @@ public class PlayerTabList { this.updatePlayer(player); player.sendHeaderAndFooter(this); }); + updateDisplayNames(); }) .repeat(Math.max(200, updateRate), TimeUnit.MILLISECONDS) .schedule(); @@ -255,6 +257,7 @@ public class PlayerTabList { this.updatePlayer(player); player.sendHeaderAndFooter(this); }); + updateDisplayNames(); } } @@ -269,7 +272,7 @@ public class PlayerTabList { * @return The servers in the same group as the given server, empty if the server is not in a group and fallback is disabled */ @NotNull - public Optional> getSiblings(String serverName) { + public Optional> getGroupNames(String serverName) { return plugin.getSettings().getServerGroups().values().stream() .filter(servers -> servers.contains(serverName)) .findFirst() @@ -285,6 +288,24 @@ public class PlayerTabList { }); } + /** + * Get the servers in the same group as the given server, as an optional list of {@link ServerInfo} + *

+ * If the server is not in a group, use the fallback group + * If the fallback is disabled, return an empty optional + * + * @param serverName The server name + * @return The servers in the same group as the given server, empty if the server is not in a group and fallback is disabled + */ + @NotNull + public List getGroupServers(String serverName) { + return plugin.getServer().getAllServers().stream() + .filter(server -> plugin.getSettings().getServerGroups().values().stream() + .filter(servers -> servers.contains(serverName)) + .anyMatch(servers -> servers.contains(server.getServerInfo().getName()))) + .toList(); + } + @Subscribe public void proxyReload(@NotNull ProxyReloadEvent event) { plugin.loadSettings();