fix: inconsistencies when players kicked/redirected on servers (#180)

Added onlyListPlayersInSameServer inside groups
Removed onlyListPlayersInSameGroup from config
Fixed problems with regex for servers
Fixed other problems
This commit is contained in:
AlexDev_ 2024-03-14 23:08:02 +01:00 committed by GitHub
parent 4e2749ac9e
commit 48b3b2af48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 154 additions and 87 deletions

View File

@ -26,8 +26,6 @@ formatter: MINEDOWN
fallback_enabled: true
# The formats to use for the fallback group.
fallback_group: default
# Only show other players on a server that is part of the same server group as the player.
only_list_players_in_same_group: true
# Define custom names to be shown in the TAB list for specific server names.
# If no custom display name is provided for a server, its original name will be used.
server_display_names:
@ -79,18 +77,19 @@ groups:
prefix: '&f%prefix%'
suffix: '&f%suffix%'
servers:
- lobby
- ^lobby[^ ]*
- survival
- creative
- minigames
- skyblock
- prison
- hub
sorting_placeholders:
- '%role_weight%'
- '%username_lower%'
collisions: false
header_footer_update_rate: 1000
placeholder_update_rate: 1000
only_list_players_in_same_server: false
```
</details>

View File

@ -27,6 +27,7 @@ import net.william278.velocitab.player.TabPlayer;
import net.william278.velocitab.tab.Nametag;
import org.apache.commons.text.StringEscapeUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.event.Level;
import java.util.List;
import java.util.Set;
@ -43,10 +44,11 @@ public record Group(
String format,
Nametag nametag,
Set<String> servers,
Set<String> sortingPlaceholders,
List<String> sortingPlaceholders,
boolean collisions,
int headerFooterUpdateRate,
int placeholderUpdateRate
int placeholderUpdateRate,
boolean onlyListPlayersInSameServer
) {
@NotNull
@ -63,7 +65,7 @@ public record Group(
@NotNull
public Set<RegisteredServer> registeredServers(@NotNull Velocitab plugin) {
if (isDefault() && plugin.getSettings().isFallbackEnabled()) {
if (isDefault(plugin) && plugin.getSettings().isFallbackEnabled()) {
return Sets.newHashSet(plugin.getServer().getAllServers());
}
return getRegexServers(plugin);
@ -73,28 +75,21 @@ public record Group(
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("");
final Matcher matcher = Pattern.compile(server, Pattern.CASE_INSENSITIVE).matcher("");
plugin.getServer().getAllServers().stream()
.filter(registeredServer -> matcher.reset(registeredServer.getServerInfo().getName()).matches())
.forEach(totalServers::add);
} catch (PatternSyntaxException ignored) {
} catch (PatternSyntaxException exception) {
plugin.log(Level.WARN, "Invalid regex pattern " + server + " in group " + name, exception);
plugin.getServer().getServer(server).ifPresent(totalServers::add);
}
}
return totalServers;
}
public boolean isDefault() {
return name.equals("default");
public boolean isDefault(@NotNull Velocitab plugin) {
return name.equals(plugin.getSettings().getFallbackGroup());
}
@NotNull
@ -106,6 +101,16 @@ public record Group(
return players;
}
@NotNull
public Set<Player> getPlayers(@NotNull Velocitab plugin, @NotNull TabPlayer tabPlayer) {
if (onlyListPlayersInSameServer) {
return tabPlayer.getPlayer().getCurrentServer()
.map(s -> Sets.newHashSet(s.getServer().getPlayersConnected()))
.orElseGet(Sets::newHashSet);
}
return getPlayers(plugin);
}
@NotNull
public Set<TabPlayer> getTabPlayers(@NotNull Velocitab plugin) {
return plugin.getTabList().getPlayers()
@ -115,6 +120,18 @@ public record Group(
.collect(Collectors.toSet());
}
@NotNull
public Set<TabPlayer> getTabPlayers(@NotNull Velocitab plugin, @NotNull TabPlayer tabPlayer) {
if (onlyListPlayersInSameServer) {
return plugin.getTabList().getPlayers()
.values()
.stream()
.filter(player -> player.getGroup().equals(this) && player.getServerName().equals(tabPlayer.getServerName()))
.collect(Collectors.toSet());
}
return getTabPlayers(plugin);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Group group)) {

View File

@ -63,9 +63,6 @@ public class Settings implements ConfigValidator {
@Comment("The formats to use for the fallback group.")
private String fallbackGroup = "default";
@Comment("Only show other players on a server that is part of the same server group as the player.")
private boolean onlyListPlayersInSameGroup = true;
@Comment("Define custom names to be shown in the TAB list for specific server names."
+ "\nIf no custom display name is provided for a server, its original name will be used.")
private Map<String, String> serverDisplayNames = Map.of("very-long-server-name", "VLSN");

View File

@ -53,10 +53,11 @@ public class TabGroups implements ConfigValidator {
"&7[%server%] &f%prefix%%username%",
new Nametag("&f%prefix%", "&f%suffix%"),
Set.of("lobby", "survival", "creative", "minigames", "skyblock", "prison", "hub"),
Set.of("%role_weight%", "%username_lower%"),
List.of("%role_weight%", "%username_lower%"),
false,
1000,
1000
1000,
false
);
public List<Group> groups = List.of(DEFAULT_GROUP);
@ -77,9 +78,20 @@ public class TabGroups implements ConfigValidator {
}
@NotNull
public Group getGroupFromServer(@NotNull String server) {
public Group getGroupFromServer(@NotNull String server, @NotNull Velocitab plugin) {
final List<Group> groups = new ArrayList<>(this.groups);
final Optional<Group> defaultGroup = getGroup("default");
// Ensure the default group is always checked last
if (defaultGroup.isPresent()) {
groups.remove(defaultGroup.get());
groups.add(defaultGroup.get());
} else {
throw new IllegalStateException("No default group found");
}
for (Group group : groups) {
if (group.servers().contains(server)) {
if (group.registeredServers(plugin)
.stream()
.anyMatch(s -> s.getServerInfo().getName().equalsIgnoreCase(server))) {
return group;
}
}
@ -101,7 +113,6 @@ public class TabGroups implements ConfigValidator {
}
final Multimap<Group, String> missingKeys = getMissingKeys();
if (missingKeys.isEmpty()) {
return;
}
@ -148,7 +159,8 @@ public class TabGroups implements ConfigValidator {
group.sortingPlaceholders() == null ? DEFAULT_GROUP.sortingPlaceholders() : group.sortingPlaceholders(),
group.collisions(),
group.headerFooterUpdateRate(),
group.placeholderUpdateRate()
group.placeholderUpdateRate(),
group.onlyListPlayersInSameServer()
);
groups.add(group);

View File

@ -175,13 +175,9 @@ public class ScoreboardManager {
}
final Player player = tabPlayer.getPlayer();
final Set<RegisteredServer> siblings = tabPlayer.getGroup().registeredServers(plugin);
final List<Player> players = siblings.stream()
.map(RegisteredServer::getPlayersConnected)
.flatMap(Collection::stream)
.toList();
final Set<Player> players = tabPlayer.getGroup().getPlayers(plugin, tabPlayer);
final List<String> roles = new ArrayList<>();
final Set<String> roles = Sets.newHashSet();
players.forEach(p -> {
if (p == player || !p.isActive()) {
return;
@ -253,8 +249,8 @@ public class ScoreboardManager {
return;
}
final Set<RegisteredServer> siblings = tabPlayer.getGroup().registeredServers(plugin);
siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> {
final Set<Player> players = tabPlayer.getGroup().getPlayers(plugin);
players.forEach(connected -> {
try {
final boolean canSee = plugin.getVanishManager().canSee(connected.getUsername(), player.getUsername());
if (!canSee) {
@ -266,7 +262,7 @@ public class ScoreboardManager {
} catch (Throwable e) {
plugin.log(Level.ERROR, "Failed to dispatch packet (unsupported client or server version)", e);
}
}));
});
}
public void registerPacket() {

View File

@ -19,7 +19,6 @@
package net.william278.velocitab.tab;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
@ -37,7 +36,9 @@ import net.william278.velocitab.config.Group;
import net.william278.velocitab.config.Placeholder;
import net.william278.velocitab.player.Role;
import net.william278.velocitab.player.TabPlayer;
import org.apache.commons.lang3.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.event.Level;
import java.util.*;
@ -97,11 +98,13 @@ public class PlayerTabList {
public void load() {
plugin.getServer().getAllPlayers().forEach(p -> {
final Optional<ServerConnection> server = p.getCurrentServer();
if (server.isEmpty()) return;
if (server.isEmpty()) {
return;
}
final String serverName = server.get().getServerInfo().getName();
final Group group = getGroup(serverName);
final boolean isDefault = !group.servers().contains(serverName);
final boolean isDefault = group.registeredServers(plugin).stream().noneMatch(s -> s.getServerInfo().getName().equals(serverName));
if (isDefault && !plugin.getSettings().isFallbackEnabled()) {
return;
@ -146,12 +149,12 @@ public class PlayerTabList {
tabPlayer.setGroup(group);
players.putIfAbsent(joined.getUniqueId(), tabPlayer);
final String serverName = getServerName(joined);
//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(""));
tabPlayer.setLastServer(serverName);
final boolean isVanished = plugin.getVanishManager().isVanished(joined.getUsername());
final boolean isDefault = group.isDefault();
final boolean isFallback = isDefault && plugin.getSettings().isFallbackEnabled();
tabPlayer.getDisplayName(plugin).thenAccept(d -> {
@ -167,16 +170,20 @@ public class PlayerTabList {
return null;
});
final Set<String> serversInGroup = group.registeredServers(plugin).stream()
.map(server -> server.getServerInfo().getName())
.collect(HashSet::new, HashSet::add, HashSet::addAll);
serversInGroup.remove(serverName);
// Update lists
plugin.getServer().getScheduler()
.buildTask(plugin, () -> {
final TabList tabList = joined.getTabList();
for (final TabPlayer player : players.values()) {
final Set<TabPlayer> tabPlayers = group.getTabPlayers(plugin);
for (final TabPlayer player : tabPlayers) {
// Skip players on other servers if the setting is enabled
if (plugin.getSettings().isOnlyListPlayersInSameGroup()
&& !isFallback &&
!group.servers().contains(player.getServerName())
) {
if (group.onlyListPlayersInSameServer() && !serverName.equals(getServerName(player.getPlayer()))) {
continue;
}
// check if current player can see the joined player
@ -187,7 +194,8 @@ public class PlayerTabList {
}
// check if joined player can see current player
if ((plugin.getVanishManager().isVanished(player.getPlayer().getUsername()) &&
!plugin.getVanishManager().canSee(joined.getUsername(), player.getPlayer().getUsername())) && player.getPlayer() != joined) {
!plugin.getVanishManager().canSee(joined.getUsername(), player.getPlayer().getUsername())) &&
player.getPlayer() != joined) {
tabList.removeEntry(player.getPlayer().getUniqueId());
} else {
tabList.getEntry(player.getPlayer().getUniqueId()).ifPresentOrElse(
@ -221,16 +229,35 @@ public class PlayerTabList {
});
}
@NotNull
private String getServerName(@NotNull Player player) {
return player.getCurrentServer()
.map(serverConnection -> serverConnection.getServerInfo().getName())
.orElse("");
}
protected void removePlayer(@NotNull Player target) {
removePlayer(target, null);
}
protected void removePlayer(@NotNull Player target, @Nullable RegisteredServer server) {
final UUID uuid = target.getUniqueId();
plugin.getServer().getAllPlayers().forEach(player -> player.getTabList().removeEntry(uuid));
final Set<Player> currentServerPlayers = Optional.ofNullable(server)
.map(RegisteredServer::getPlayersConnected)
.map(HashSet::new)
.orElseGet(HashSet::new);
currentServerPlayers.add(target);
// Update the tab list of all players
plugin.getServer().getScheduler()
.buildTask(plugin, () -> getPlayers().values().forEach(player -> {
player.getPlayer().getTabList().removeEntry(uuid);
player.sendHeaderAndFooter(this);
}))
.buildTask(plugin, () -> getPlayers().values().stream()
.filter(p -> currentServerPlayers.isEmpty() || !currentServerPlayers.contains(p.getPlayer()))
.forEach(player -> {
player.getPlayer().getTabList().removeEntry(uuid);
player.sendHeaderAndFooter(this);
}))
.delay(500, TimeUnit.MILLISECONDS)
.schedule();
// Delete player team
@ -240,20 +267,15 @@ public class PlayerTabList {
}
@NotNull
CompletableFuture<TabListEntry> createEntry(@NotNull TabPlayer player, @NotNull TabList tabList) {
return player.getDisplayName(plugin).thenApply(name -> TabListEntry.builder()
.profile(player.getPlayer().getGameProfile())
.displayName(name)
.latency(0)
.tabList(tabList)
.build());
protected CompletableFuture<TabListEntry> createEntry(@NotNull TabPlayer player, @NotNull TabList tabList) {
return player.getDisplayName(plugin).thenApply(name -> createEntry(player, tabList, name));
}
protected TabListEntry createEntry(@NotNull TabPlayer player, @NotNull TabList tabList, @NotNull Component displayName) {
return TabListEntry.builder()
.profile(player.getPlayer().getGameProfile())
.displayName(displayName)
.latency(0)
.latency(Math.max((int) player.getPlayer().getPing(), 0))
.tabList(tabList)
.build();
}
@ -314,15 +336,19 @@ public class PlayerTabList {
}
final boolean isVanished = plugin.getVanishManager().isVanished(tabPlayer.getPlayer().getUsername());
final Set<TabPlayer> players = tabPlayer.getGroup().getTabPlayers(plugin, tabPlayer);
players.values().forEach(player -> {
players.forEach(player -> {
if (isVanished && !plugin.getVanishManager().canSee(player.getPlayer().getUsername(), tabPlayer.getPlayer().getUsername())) {
return;
}
player.getPlayer().getTabList().getEntries().stream()
.filter(e -> e.getProfile().getId().equals(tabPlayer.getPlayer().getUniqueId())).findFirst()
.ifPresent(entry -> entry.setDisplayName(displayName));
.ifPresent(entry -> {
entry.setDisplayName(displayName);
entry.setLatency(Math.max((int) tabPlayer.getPlayer().getPing(), 0));
});
});
});
}
@ -379,7 +405,7 @@ public class PlayerTabList {
* @param incrementIndexes Whether to increment the header and footer indexes.
*/
private void updateGroupPlayers(@NotNull Group group, boolean all, boolean incrementIndexes) {
Set<TabPlayer> groupPlayers = group.getTabPlayers(plugin);
final Set<TabPlayer> groupPlayers = group.getTabPlayers(plugin);
if (groupPlayers.isEmpty()) {
return;
}
@ -449,7 +475,7 @@ public class PlayerTabList {
@NotNull
public Group getGroup(@NotNull String serverName) {
return plugin.getTabGroups().getGroupFromServer(serverName);
return plugin.getTabGroups().getGroupFromServer(serverName, plugin);
}

View File

@ -19,6 +19,7 @@
package net.william278.velocitab.tab;
import com.google.common.collect.Sets;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
@ -35,6 +36,7 @@ import net.william278.velocitab.player.TabPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@ -46,20 +48,34 @@ public class TabListListener {
private final Velocitab plugin;
private final PlayerTabList tabList;
// In 1.8 there is a packet delay problem
private final Set<UUID> justQuit;
public TabListListener(@NotNull Velocitab plugin, @NotNull PlayerTabList tabList) {
this.plugin = plugin;
this.tabList = tabList;
this.justQuit = Sets.newConcurrentHashSet();
}
@Subscribe
public void onKick(@NotNull KickedFromServerEvent event) {
event.getPlayer().getTabList().clearAll();
event.getPlayer().getTabList().getEntries().stream()
.filter(entry -> entry.getProfile() != null && !entry.getProfile().getId().equals(event.getPlayer().getUniqueId()))
.forEach(entry -> event.getPlayer().getTabList().removeEntry(entry.getProfile().getId()));
event.getPlayer().getTabList().clearHeaderAndFooter();
if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer || event.getResult() instanceof KickedFromServerEvent.RedirectPlayer) {
if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) {
tabList.removePlayer(event.getPlayer());
} else if (event.getResult() instanceof KickedFromServerEvent.RedirectPlayer redirectPlayer) {
tabList.removePlayer(event.getPlayer(), redirectPlayer.getServer());
}
justQuit.add(event.getPlayer().getUniqueId());
plugin.getServer().getScheduler().buildTask(plugin,
() -> justQuit.remove(event.getPlayer().getUniqueId()))
.delay(300, TimeUnit.MILLISECONDS)
.schedule();
}
@SuppressWarnings("UnstableApiUsage")
@ -73,7 +89,8 @@ public class TabListListener {
.orElse("");
final Group group = tabList.getGroup(serverName);
plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(joined, group));
final boolean isDefault = !group.servers().contains(serverName);
final boolean isDefault = group.registeredServers(plugin).stream()
.noneMatch(server -> server.getServerInfo().getName().equalsIgnoreCase(serverName));
// If the server is not in a group, use fallback.
// If fallback is disabled, permit the player to switch excluded servers without a header or footer override
@ -92,15 +109,15 @@ public class TabListListener {
final Component displayName = tabPlayer.get().getLastDisplayName();
plugin.getServer().getScheduler().buildTask(plugin, () -> {
if (header.equals(event.getPlayer().getPlayerListHeader()) && footer.equals(event.getPlayer().getPlayerListFooter())) {
event.getPlayer().sendPlayerListHeaderAndFooter(header, footer);
event.getPlayer().getCurrentServer().ifPresent(serverConnection ->
serverConnection.getServer().getPlayersConnected().forEach(player ->
player.getTabList().getEntry(joined.getUniqueId()).ifPresent(entry -> {
if (entry.getDisplayNameComponent().isPresent() && entry.getDisplayNameComponent().get().equals(displayName)) {
entry.setDisplayName(Component.text(joined.getUsername()));
}
})));
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()))
) {
joined.sendPlayerListHeaderAndFooter(Component.empty(), Component.empty());
joined.getCurrentServer().ifPresent(serverConnection -> serverConnection.getServer().getPlayersConnected().forEach(player ->
player.getTabList().getEntry(joined.getUniqueId())
.ifPresent(entry -> entry.setDisplayName(Component.text(joined.getUsername())))));
}
}).delay(500, TimeUnit.MILLISECONDS).schedule();
@ -108,6 +125,14 @@ public class TabListListener {
return;
}
if (justQuit.contains(joined.getUniqueId())) {
plugin.getServer().getScheduler().buildTask(plugin,
() -> tabList.joinPlayer(joined, group))
.delay(250, TimeUnit.MILLISECONDS)
.schedule();
return;
}
tabList.joinPlayer(joined, group);
}
@ -118,9 +143,6 @@ public class TabListListener {
return;
}
// Remove the player from the tracking list, Print warning if player was not removed
final UUID uuid = event.getPlayer().getUniqueId();
// Remove the player from the tab list of all other players
tabList.removePlayer(event.getPlayer());
}

View File

@ -27,15 +27,16 @@ import org.jetbrains.annotations.NotNull;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* The VanishTabList handles the tab list for vanished players
*/
public class VanishTabList {
private final Velocitab plugin;
private final PlayerTabList tabList;
public VanishTabList(Velocitab plugin, PlayerTabList tabList) {
this.plugin = plugin;
this.tabList = tabList;
@ -79,8 +80,6 @@ public class VanishTabList {
*/
public void recalculateVanishForPlayer(@NotNull TabPlayer tabPlayer) {
final Player player = tabPlayer.getPlayer();
final Set<String> serversInGroup = tabPlayer.getGroup().servers();
plugin.getServer().getAllPlayers().forEach(p -> {
if (p.equals(player)) {
return;
@ -94,8 +93,8 @@ public class VanishTabList {
final TabPlayer target = targetOptional.get();
final String serverName = target.getServerName();
if (plugin.getSettings().isOnlyListPlayersInSameGroup()
&& !serversInGroup.contains(serverName)) {
if (tabPlayer.getGroup().onlyListPlayersInSameServer()
&& !tabPlayer.getServerName().equals(serverName)) {
return;
}
@ -115,5 +114,4 @@ public class VanishTabList {
}
});
}
}