diff --git a/docs/Config-File.md b/docs/Config-File.md index 72303379..0eeda573 100644 --- a/docs/Config-File.md +++ b/docs/Config-File.md @@ -17,6 +17,8 @@ The config file is located in `/plugins/velocitab/config.yml` and the tab groups check_for_updates: true # Whether to remove nametag from players' heads if the nametag associated with their server group is empty. remove_nametags: true +# Whether to disable header and footer if they are empty and let backend servers handle them. +disable_header_footer_if_empty: true # Which text formatter to use (MINEDOWN, MINIMESSAGE, or LEGACY) formatter: MINEDOWN # All servers which are not in other groups will be put in the fallback group. @@ -33,16 +35,21 @@ server_display_names: # Whether to enable the PAPIProxyBridge hook for PAPI support enable_papi_hook: true # How long in seconds to cache PAPI placeholders for, in milliseconds. (0 to disable) -papi_cache_time: 200 +papi_cache_time: 30000 # If you are using MINIMESSAGE formatting, enable this to support MiniPlaceholders in formatting. enable_mini_placeholders_hook: true # Whether to send scoreboard teams packets. Required for player list sorting and nametag formatting. # Turn this off if you're using scoreboard teams on backend servers. send_scoreboard_packets: true +# If built-in placeholders return a blank string, fallback to Placeholder API equivalents. +# For example, if %prefix% returns a blank string, use %luckperms_prefix%. Requires PAPIProxyBridge. +fallback_to_papi_if_placeholder_blank: false # Whether to sort players in the TAB list. sort_players: true # Remove gamemode spectator effect for other players in the TAB list. remove_spectator_effect: false +# Whether to enable the Plugin Message API (allows backend plugins to perform certain operations) +enable_plugin_message_api: true ``` diff --git a/gradle.properties b/gradle.properties index 277ed307..176ce93a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ javaVersion=17 org.gradle.jvmargs='-Dfile.encoding=UTF-8' org.gradle.daemon=true -plugin_version=1.6.3 +plugin_version=1.6.4 plugin_archive=velocitab plugin_description=A beautiful and versatile TAB list plugin for Velocity proxies diff --git a/src/main/java/net/william278/velocitab/config/Group.java b/src/main/java/net/william278/velocitab/config/Group.java index 2d10f508..5db1f376 100644 --- a/src/main/java/net/william278/velocitab/config/Group.java +++ b/src/main/java/net/william278/velocitab/config/Group.java @@ -19,6 +19,7 @@ package net.william278.velocitab.config; +import com.google.common.collect.Sets; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.server.RegisteredServer; import net.william278.velocitab.Velocitab; @@ -27,9 +28,12 @@ import org.apache.commons.text.StringEscapeUtils; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; import java.util.List; -import java.util.Optional; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; @SuppressWarnings("unused") public record Group( @@ -38,8 +42,8 @@ public record Group( List footers, String format, Nametag nametag, - List servers, - List sortingPlaceholders, + Set servers, + Set sortingPlaceholders, boolean collisions, int headerFooterUpdateRate, int placeholderUpdateRate @@ -58,15 +62,35 @@ public String getFooter(int index) { } @NotNull - public List registeredServers(Velocitab plugin) { + public Set registeredServers(@NotNull Velocitab plugin) { if (isDefault() && plugin.getSettings().isFallbackEnabled()) { - return new ArrayList<>(plugin.getServer().getAllServers()); + return Sets.newHashSet(plugin.getServer().getAllServers()); } - return servers.stream() - .map(plugin.getServer()::getServer) - .filter(Optional::isPresent) - .map(Optional::get) - .toList(); + return getRegexServers(plugin); + } + + @NotNull + private Set getRegexServers(@NotNull Velocitab plugin) { + final Set totalServers = Sets.newHashSet(); + for (String server : servers) { + if (!server.contains("*") && !server.contains(".")) { + plugin.getServer().getServer(server).ifPresent(totalServers::add); + continue; + } + + try { + final Matcher matcher = Pattern.compile(server + .replace(".", "\\.") + .replace("*", ".*")) + .matcher(""); + plugin.getServer().getAllServers().stream() + .filter(registeredServer -> matcher.reset(registeredServer.getServerInfo().getName()).matches()) + .forEach(totalServers::add); + } catch (PatternSyntaxException ignored) { + plugin.getServer().getServer(server).ifPresent(totalServers::add); + } + } + return totalServers; } public boolean isDefault() { @@ -74,8 +98,8 @@ public boolean isDefault() { } @NotNull - public List getPlayers(Velocitab plugin) { - List players = new ArrayList<>(); + public Set getPlayers(@NotNull Velocitab plugin) { + Set players = Sets.newHashSet(); for (RegisteredServer server : registeredServers(plugin)) { players.addAll(server.getPlayersConnected()); } @@ -83,12 +107,12 @@ public List getPlayers(Velocitab plugin) { } @NotNull - public List getTabPlayers(Velocitab plugin) { + public Set getTabPlayers(@NotNull Velocitab plugin) { return plugin.getTabList().getPlayers() .values() .stream() .filter(tabPlayer -> tabPlayer.getGroup().equals(this)) - .toList(); + .collect(Collectors.toSet()); } @Override diff --git a/src/main/java/net/william278/velocitab/config/Settings.java b/src/main/java/net/william278/velocitab/config/Settings.java index ed3a6fb5..86d83c86 100644 --- a/src/main/java/net/william278/velocitab/config/Settings.java +++ b/src/main/java/net/william278/velocitab/config/Settings.java @@ -50,6 +50,9 @@ public class Settings implements ConfigValidator { @Comment("Whether to remove nametag from players' heads if the nametag associated with their server group is empty.") private boolean removeNametags = false; + @Comment("Whether to disable header and footer if they are empty and let backend servers handle them.") + private boolean disableHeaderFooterIfEmpty = true; + @Comment("Which text formatter to use (MINEDOWN, MINIMESSAGE, or LEGACY)") private Formatter formatter = Formatter.MINEDOWN; diff --git a/src/main/java/net/william278/velocitab/config/TabGroups.java b/src/main/java/net/william278/velocitab/config/TabGroups.java index d4d12845..c1cbe2a5 100644 --- a/src/main/java/net/william278/velocitab/config/TabGroups.java +++ b/src/main/java/net/william278/velocitab/config/TabGroups.java @@ -19,6 +19,7 @@ package net.william278.velocitab.config; +import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import de.exlll.configlib.Configuration; @@ -29,10 +30,7 @@ import net.william278.velocitab.tab.Nametag; import org.jetbrains.annotations.NotNull; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; +import java.util.*; @SuppressWarnings("FieldMayBeFinal") @Getter @@ -54,8 +52,8 @@ public class TabGroups implements ConfigValidator { List.of("[There are currently %players_online%/%max_players_online% players online](gray)"), "&7[%server%] &f%prefix%%username%", new Nametag("&f%prefix%", "&f%suffix%"), - List.of("lobby", "survival", "creative", "minigames", "skyblock", "prison", "hub"), - List.of("%role_weight%", "%username_lower%"), + Set.of("lobby", "survival", "creative", "minigames", "skyblock", "prison", "hub"), + Set.of("%role_weight%", "%username_lower%"), false, 1000, 1000 @@ -113,7 +111,7 @@ public void validateConfig(@NotNull Velocitab plugin) { @NotNull private Multimap getMissingKeys() { - final Multimap missingKeys = Multimaps.newSetMultimap(new HashMap<>(), HashSet::new); + final Multimap missingKeys = Multimaps.newSetMultimap(Maps.newHashMap(), HashSet::new); for (Group group : groups) { if (group.format() == null) { diff --git a/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java b/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java index c62ce2e8..b83bec0e 100644 --- a/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java +++ b/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java @@ -113,7 +113,7 @@ private void handleVanish(@NotNull TabPlayer tabPlayer, boolean vanish) { if (teamName == null) { return; } - final List siblings = tabPlayer.getGroup().registeredServers(plugin); + final Set siblings = tabPlayer.getGroup().registeredServers(plugin); final Optional cachedTag = Optional.ofNullable(nametags.getOrDefault(teamName, null)); cachedTag.ifPresent(nametag -> { @@ -175,7 +175,7 @@ public void resendAllTeams(@NotNull TabPlayer tabPlayer) { } final Player player = tabPlayer.getPlayer(); - final List siblings = tabPlayer.getGroup().registeredServers(plugin); + final Set siblings = tabPlayer.getGroup().registeredServers(plugin); final List players = siblings.stream() .map(RegisteredServer::getPlayersConnected) .flatMap(Collection::stream) @@ -253,7 +253,7 @@ private void dispatchGroupPacket(@NotNull UpdateTeamsPacket packet, @NotNull Tab return; } - final List siblings = tabPlayer.getGroup().registeredServers(plugin); + final Set siblings = tabPlayer.getGroup().registeredServers(plugin); siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> { try { final boolean canSee = plugin.getVanishManager().canSee(connected.getUsername(), player.getUsername()); diff --git a/src/main/java/net/william278/velocitab/player/TabPlayer.java b/src/main/java/net/william278/velocitab/player/TabPlayer.java index 4719114f..596128b4 100644 --- a/src/main/java/net/william278/velocitab/player/TabPlayer.java +++ b/src/main/java/net/william278/velocitab/player/TabPlayer.java @@ -41,6 +41,7 @@ @ToString public final class TabPlayer implements Comparable { + private final Velocitab plugin; private final Player player; @Setter private Role role; @@ -65,7 +66,9 @@ public final class TabPlayer implements Comparable { @Setter private boolean loaded; - public TabPlayer(@NotNull Player player, @NotNull Role role, @NotNull Group group) { + public TabPlayer(@NotNull Velocitab plugin, @NotNull Player player, + @NotNull Role role, @NotNull Group group) { + this.plugin = plugin; this.player = player; this.role = role; this.group = group; @@ -134,12 +137,23 @@ public Optional getLastTeamName() { } public CompletableFuture sendHeaderAndFooter(@NotNull PlayerTabList tabList) { - return tabList.getHeader(this).thenCompose(header -> tabList.getFooter(this) - .thenAccept(footer -> { + return tabList.getHeader(this).thenCompose(header -> tabList.getFooter(this).thenAccept(footer -> { + final boolean disabled = plugin.getSettings().isDisableHeaderFooterIfEmpty(); + if (disabled) { + if (!Component.empty().equals(header)) { lastHeader = header; + player.sendPlayerListHeader(header); + } + if (!Component.empty().equals(footer)) { lastFooter = footer; - player.sendPlayerListHeaderAndFooter(header, footer); - })); + player.sendPlayerListFooter(footer); + } + } else { + lastHeader = header; + lastFooter = footer; + player.sendPlayerListHeaderAndFooter(header, footer); + } + })); } public void incrementIndexes() { diff --git a/src/main/java/net/william278/velocitab/tab/PlayerTabList.java b/src/main/java/net/william278/velocitab/tab/PlayerTabList.java index 054928c3..2a69969f 100644 --- a/src/main/java/net/william278/velocitab/tab/PlayerTabList.java +++ b/src/main/java/net/william278/velocitab/tab/PlayerTabList.java @@ -129,7 +129,7 @@ public void close() { return; } - final List serversInGroup = Lists.newArrayList(tabPlayer.getGroup().registeredServers(plugin)); + final Set serversInGroup = tabPlayer.getGroup().registeredServers(plugin); if (serversInGroup.isEmpty()) { return; } @@ -283,7 +283,7 @@ private void addPlayerToTabList(@NotNull TabPlayer player, @NotNull TabPlayer ne @NotNull public TabPlayer createTabPlayer(@NotNull Player player, @NotNull Group group) { - return new TabPlayer(player, + return new TabPlayer(plugin, player, plugin.getLuckPermsHook().map(hook -> hook.getPlayerRole(player)).orElse(Role.DEFAULT_ROLE), group ); @@ -379,7 +379,7 @@ private void updatePeriodically(Group group) { * @param incrementIndexes Whether to increment the header and footer indexes. */ private void updateGroupPlayers(@NotNull Group group, boolean all, boolean incrementIndexes) { - List groupPlayers = group.getTabPlayers(plugin); + Set groupPlayers = group.getTabPlayers(plugin); if (groupPlayers.isEmpty()) { return; } diff --git a/src/main/java/net/william278/velocitab/tab/TabListListener.java b/src/main/java/net/william278/velocitab/tab/TabListListener.java index b5ec226b..f45acbbd 100644 --- a/src/main/java/net/william278/velocitab/tab/TabListListener.java +++ b/src/main/java/net/william278/velocitab/tab/TabListListener.java @@ -114,6 +114,7 @@ public void onPlayerJoin(@NotNull ServerPostConnectEvent event) { @Subscribe(order = PostOrder.LAST) public void onPlayerQuit(@NotNull DisconnectEvent event) { if (event.getLoginStatus() != DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN) { + checkDelayedDisconnect(event); return; } @@ -124,6 +125,21 @@ public void onPlayerQuit(@NotNull DisconnectEvent event) { tabList.removePlayer(event.getPlayer()); } + private void checkDelayedDisconnect(@NotNull DisconnectEvent event) { + final Player player = event.getPlayer(); + plugin.getServer().getScheduler().buildTask(plugin, () -> { + final Optional actualPlayer = plugin.getServer().getPlayer(player.getUniqueId()); + if (actualPlayer.isPresent() && !actualPlayer.get().equals(player)) { + return; + } + if (player.getCurrentServer().isPresent()) { + return; + } + tabList.removePlayer(player); + plugin.log("Player " + player.getUsername() + " was not removed from the tab list, removing now."); + }).delay(500, TimeUnit.MILLISECONDS).schedule(); + } + @Subscribe public void proxyReload(@NotNull ProxyReloadEvent event) { plugin.loadConfigs(); diff --git a/src/main/java/net/william278/velocitab/tab/VanishTabList.java b/src/main/java/net/william278/velocitab/tab/VanishTabList.java index 992c1341..40a24944 100644 --- a/src/main/java/net/william278/velocitab/tab/VanishTabList.java +++ b/src/main/java/net/william278/velocitab/tab/VanishTabList.java @@ -24,8 +24,8 @@ import net.william278.velocitab.player.TabPlayer; import org.jetbrains.annotations.NotNull; -import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.UUID; /** @@ -79,7 +79,7 @@ public void unVanishPlayer(@NotNull TabPlayer tabPlayer) { */ public void recalculateVanishForPlayer(@NotNull TabPlayer tabPlayer) { final Player player = tabPlayer.getPlayer(); - final List serversInGroup = tabPlayer.getGroup().servers(); + final Set serversInGroup = tabPlayer.getGroup().servers(); plugin.getServer().getAllPlayers().forEach(p -> { if (p.equals(player)) {