Added regex system and fixed ghost players bug (#176)

* Added regex system for TabGroup's servers.
Fixed ghost player after kick/disconnect.

* Fixed config docs with missing entries

* Bumped version
This commit is contained in:
AlexDev_ 2024-03-11 19:43:34 +01:00 committed by GitHub
parent c0abf481c1
commit 4e2749ac9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 99 additions and 37 deletions

View File

@ -17,6 +17,8 @@ The config file is located in `/plugins/velocitab/config.yml` and the tab groups
check_for_updates: true check_for_updates: true
# Whether to remove nametag from players' heads if the nametag associated with their server group is empty. # Whether to remove nametag from players' heads if the nametag associated with their server group is empty.
remove_nametags: true 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) # Which text formatter to use (MINEDOWN, MINIMESSAGE, or LEGACY)
formatter: MINEDOWN formatter: MINEDOWN
# All servers which are not in other groups will be put in the fallback group. # 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 # Whether to enable the PAPIProxyBridge hook for PAPI support
enable_papi_hook: true enable_papi_hook: true
# How long in seconds to cache PAPI placeholders for, in milliseconds. (0 to disable) # 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. # If you are using MINIMESSAGE formatting, enable this to support MiniPlaceholders in formatting.
enable_mini_placeholders_hook: true enable_mini_placeholders_hook: true
# Whether to send scoreboard teams packets. Required for player list sorting and nametag formatting. # 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. # Turn this off if you're using scoreboard teams on backend servers.
send_scoreboard_packets: true 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. # Whether to sort players in the TAB list.
sort_players: true sort_players: true
# Remove gamemode spectator effect for other players in the TAB list. # Remove gamemode spectator effect for other players in the TAB list.
remove_spectator_effect: false remove_spectator_effect: false
# Whether to enable the Plugin Message API (allows backend plugins to perform certain operations)
enable_plugin_message_api: true
``` ```
</details> </details>

View File

@ -3,7 +3,7 @@ javaVersion=17
org.gradle.jvmargs='-Dfile.encoding=UTF-8' org.gradle.jvmargs='-Dfile.encoding=UTF-8'
org.gradle.daemon=true org.gradle.daemon=true
plugin_version=1.6.3 plugin_version=1.6.4
plugin_archive=velocitab plugin_archive=velocitab
plugin_description=A beautiful and versatile TAB list plugin for Velocity proxies plugin_description=A beautiful and versatile TAB list plugin for Velocity proxies

View File

@ -19,6 +19,7 @@
package net.william278.velocitab.config; package net.william278.velocitab.config;
import com.google.common.collect.Sets;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.william278.velocitab.Velocitab; import net.william278.velocitab.Velocitab;
@ -27,9 +28,12 @@ import net.william278.velocitab.tab.Nametag;
import org.apache.commons.text.StringEscapeUtils; import org.apache.commons.text.StringEscapeUtils;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List; 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") @SuppressWarnings("unused")
public record Group( public record Group(
@ -38,8 +42,8 @@ public record Group(
List<String> footers, List<String> footers,
String format, String format,
Nametag nametag, Nametag nametag,
List<String> servers, Set<String> servers,
List<String> sortingPlaceholders, Set<String> sortingPlaceholders,
boolean collisions, boolean collisions,
int headerFooterUpdateRate, int headerFooterUpdateRate,
int placeholderUpdateRate int placeholderUpdateRate
@ -58,15 +62,35 @@ public record Group(
} }
@NotNull @NotNull
public List<RegisteredServer> registeredServers(Velocitab plugin) { public Set<RegisteredServer> registeredServers(@NotNull Velocitab plugin) {
if (isDefault() && plugin.getSettings().isFallbackEnabled()) { if (isDefault() && plugin.getSettings().isFallbackEnabled()) {
return new ArrayList<>(plugin.getServer().getAllServers()); return Sets.newHashSet(plugin.getServer().getAllServers());
} }
return servers.stream() return getRegexServers(plugin);
.map(plugin.getServer()::getServer) }
.filter(Optional::isPresent)
.map(Optional::get) @NotNull
.toList(); private Set<RegisteredServer> getRegexServers(@NotNull Velocitab plugin) {
final Set<RegisteredServer> 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() { public boolean isDefault() {
@ -74,8 +98,8 @@ public record Group(
} }
@NotNull @NotNull
public List<Player> getPlayers(Velocitab plugin) { public Set<Player> getPlayers(@NotNull Velocitab plugin) {
List<Player> players = new ArrayList<>(); Set<Player> players = Sets.newHashSet();
for (RegisteredServer server : registeredServers(plugin)) { for (RegisteredServer server : registeredServers(plugin)) {
players.addAll(server.getPlayersConnected()); players.addAll(server.getPlayersConnected());
} }
@ -83,12 +107,12 @@ public record Group(
} }
@NotNull @NotNull
public List<TabPlayer> getTabPlayers(Velocitab plugin) { public Set<TabPlayer> getTabPlayers(@NotNull Velocitab plugin) {
return plugin.getTabList().getPlayers() return plugin.getTabList().getPlayers()
.values() .values()
.stream() .stream()
.filter(tabPlayer -> tabPlayer.getGroup().equals(this)) .filter(tabPlayer -> tabPlayer.getGroup().equals(this))
.toList(); .collect(Collectors.toSet());
} }
@Override @Override

View File

@ -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.") @Comment("Whether to remove nametag from players' heads if the nametag associated with their server group is empty.")
private boolean removeNametags = false; 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)") @Comment("Which text formatter to use (MINEDOWN, MINIMESSAGE, or LEGACY)")
private Formatter formatter = Formatter.MINEDOWN; private Formatter formatter = Formatter.MINEDOWN;

View File

@ -19,6 +19,7 @@
package net.william278.velocitab.config; package net.william278.velocitab.config;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps; import com.google.common.collect.Multimaps;
import de.exlll.configlib.Configuration; import de.exlll.configlib.Configuration;
@ -29,10 +30,7 @@ import net.william278.velocitab.Velocitab;
import net.william278.velocitab.tab.Nametag; import net.william278.velocitab.tab.Nametag;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.HashMap; import java.util.*;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
@SuppressWarnings("FieldMayBeFinal") @SuppressWarnings("FieldMayBeFinal")
@Getter @Getter
@ -54,8 +52,8 @@ public class TabGroups implements ConfigValidator {
List.of("[There are currently %players_online%/%max_players_online% players online](gray)"), List.of("[There are currently %players_online%/%max_players_online% players online](gray)"),
"&7[%server%] &f%prefix%%username%", "&7[%server%] &f%prefix%%username%",
new Nametag("&f%prefix%", "&f%suffix%"), new Nametag("&f%prefix%", "&f%suffix%"),
List.of("lobby", "survival", "creative", "minigames", "skyblock", "prison", "hub"), Set.of("lobby", "survival", "creative", "minigames", "skyblock", "prison", "hub"),
List.of("%role_weight%", "%username_lower%"), Set.of("%role_weight%", "%username_lower%"),
false, false,
1000, 1000,
1000 1000
@ -113,7 +111,7 @@ public class TabGroups implements ConfigValidator {
@NotNull @NotNull
private Multimap<Group, String> getMissingKeys() { private Multimap<Group, String> getMissingKeys() {
final Multimap<Group, String> missingKeys = Multimaps.newSetMultimap(new HashMap<>(), HashSet::new); final Multimap<Group, String> missingKeys = Multimaps.newSetMultimap(Maps.newHashMap(), HashSet::new);
for (Group group : groups) { for (Group group : groups) {
if (group.format() == null) { if (group.format() == null) {

View File

@ -113,7 +113,7 @@ public class ScoreboardManager {
if (teamName == null) { if (teamName == null) {
return; return;
} }
final List<RegisteredServer> siblings = tabPlayer.getGroup().registeredServers(plugin); final Set<RegisteredServer> siblings = tabPlayer.getGroup().registeredServers(plugin);
final Optional<Nametag> cachedTag = Optional.ofNullable(nametags.getOrDefault(teamName, null)); final Optional<Nametag> cachedTag = Optional.ofNullable(nametags.getOrDefault(teamName, null));
cachedTag.ifPresent(nametag -> { cachedTag.ifPresent(nametag -> {
@ -175,7 +175,7 @@ public class ScoreboardManager {
} }
final Player player = tabPlayer.getPlayer(); final Player player = tabPlayer.getPlayer();
final List<RegisteredServer> siblings = tabPlayer.getGroup().registeredServers(plugin); final Set<RegisteredServer> siblings = tabPlayer.getGroup().registeredServers(plugin);
final List<Player> players = siblings.stream() final List<Player> players = siblings.stream()
.map(RegisteredServer::getPlayersConnected) .map(RegisteredServer::getPlayersConnected)
.flatMap(Collection::stream) .flatMap(Collection::stream)
@ -253,7 +253,7 @@ public class ScoreboardManager {
return; return;
} }
final List<RegisteredServer> siblings = tabPlayer.getGroup().registeredServers(plugin); final Set<RegisteredServer> siblings = tabPlayer.getGroup().registeredServers(plugin);
siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> { siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> {
try { try {
final boolean canSee = plugin.getVanishManager().canSee(connected.getUsername(), player.getUsername()); final boolean canSee = plugin.getVanishManager().canSee(connected.getUsername(), player.getUsername());

View File

@ -41,6 +41,7 @@ import java.util.concurrent.CompletableFuture;
@ToString @ToString
public final class TabPlayer implements Comparable<TabPlayer> { public final class TabPlayer implements Comparable<TabPlayer> {
private final Velocitab plugin;
private final Player player; private final Player player;
@Setter @Setter
private Role role; private Role role;
@ -65,7 +66,9 @@ public final class TabPlayer implements Comparable<TabPlayer> {
@Setter @Setter
private boolean loaded; 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.player = player;
this.role = role; this.role = role;
this.group = group; this.group = group;
@ -134,11 +137,22 @@ public final class TabPlayer implements Comparable<TabPlayer> {
} }
public CompletableFuture<Void> sendHeaderAndFooter(@NotNull PlayerTabList tabList) { public CompletableFuture<Void> sendHeaderAndFooter(@NotNull PlayerTabList tabList) {
return tabList.getHeader(this).thenCompose(header -> tabList.getFooter(this) return tabList.getHeader(this).thenCompose(header -> tabList.getFooter(this).thenAccept(footer -> {
.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.sendPlayerListFooter(footer);
}
} else {
lastHeader = header; lastHeader = header;
lastFooter = footer; lastFooter = footer;
player.sendPlayerListHeaderAndFooter(header, footer); player.sendPlayerListHeaderAndFooter(header, footer);
}
})); }));
} }

View File

@ -129,7 +129,7 @@ public class PlayerTabList {
return; return;
} }
final List<RegisteredServer> serversInGroup = Lists.newArrayList(tabPlayer.getGroup().registeredServers(plugin)); final Set<RegisteredServer> serversInGroup = tabPlayer.getGroup().registeredServers(plugin);
if (serversInGroup.isEmpty()) { if (serversInGroup.isEmpty()) {
return; return;
} }
@ -283,7 +283,7 @@ public class PlayerTabList {
@NotNull @NotNull
public TabPlayer createTabPlayer(@NotNull Player player, @NotNull Group group) { 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), plugin.getLuckPermsHook().map(hook -> hook.getPlayerRole(player)).orElse(Role.DEFAULT_ROLE),
group group
); );
@ -379,7 +379,7 @@ public class PlayerTabList {
* @param incrementIndexes Whether to increment the header and footer indexes. * @param incrementIndexes Whether to increment the header and footer indexes.
*/ */
private void updateGroupPlayers(@NotNull Group group, boolean all, boolean incrementIndexes) { private void updateGroupPlayers(@NotNull Group group, boolean all, boolean incrementIndexes) {
List<TabPlayer> groupPlayers = group.getTabPlayers(plugin); Set<TabPlayer> groupPlayers = group.getTabPlayers(plugin);
if (groupPlayers.isEmpty()) { if (groupPlayers.isEmpty()) {
return; return;
} }

View File

@ -114,6 +114,7 @@ public class TabListListener {
@Subscribe(order = PostOrder.LAST) @Subscribe(order = PostOrder.LAST)
public void onPlayerQuit(@NotNull DisconnectEvent event) { public void onPlayerQuit(@NotNull DisconnectEvent event) {
if (event.getLoginStatus() != DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN) { if (event.getLoginStatus() != DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN) {
checkDelayedDisconnect(event);
return; return;
} }
@ -124,6 +125,21 @@ public class TabListListener {
tabList.removePlayer(event.getPlayer()); tabList.removePlayer(event.getPlayer());
} }
private void checkDelayedDisconnect(@NotNull DisconnectEvent event) {
final Player player = event.getPlayer();
plugin.getServer().getScheduler().buildTask(plugin, () -> {
final Optional<Player> 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 @Subscribe
public void proxyReload(@NotNull ProxyReloadEvent event) { public void proxyReload(@NotNull ProxyReloadEvent event) {
plugin.loadConfigs(); plugin.loadConfigs();

View File

@ -24,8 +24,8 @@ import net.william278.velocitab.Velocitab;
import net.william278.velocitab.player.TabPlayer; import net.william278.velocitab.player.TabPlayer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
/** /**
@ -79,7 +79,7 @@ public class VanishTabList {
*/ */
public void recalculateVanishForPlayer(@NotNull TabPlayer tabPlayer) { public void recalculateVanishForPlayer(@NotNull TabPlayer tabPlayer) {
final Player player = tabPlayer.getPlayer(); final Player player = tabPlayer.getPlayer();
final List<String> serversInGroup = tabPlayer.getGroup().servers(); final Set<String> serversInGroup = tabPlayer.getGroup().servers();
plugin.getServer().getAllPlayers().forEach(p -> { plugin.getServer().getAllPlayers().forEach(p -> {
if (p.equals(player)) { if (p.equals(player)) {