From b7c353a0ec3dfc114b4329274c560686df15c84b Mon Sep 17 00:00:00 2001 From: AlexDev_ <56083016+alexdev03@users.noreply.github.com> Date: Sat, 30 Mar 2024 00:25:38 +0100 Subject: [PATCH] feat: add `show_all_players_from_all_groups` config option (#183) Code refactor Improved system that handles latency --- docs/Config-File.md | 2 + .../william278/velocitab/config/Group.java | 27 +++++- .../william278/velocitab/config/Settings.java | 3 + .../velocitab/config/TabGroups.java | 2 +- .../velocitab/sorting/SortingManager.java | 5 +- .../william278/velocitab/tab/GroupTasks.java | 39 +++++++++ .../velocitab/tab/PlayerTabList.java | 86 ++++++++++--------- 7 files changed, 118 insertions(+), 46 deletions(-) create mode 100644 src/main/java/net/william278/velocitab/tab/GroupTasks.java diff --git a/docs/Config-File.md b/docs/Config-File.md index ac82045..c7e25be 100644 --- a/docs/Config-File.md +++ b/docs/Config-File.md @@ -26,6 +26,8 @@ formatter: MINEDOWN fallback_enabled: true # The formats to use for the fallback group. fallback_group: default +# Whether to show all players from all groups in the TAB list. +show_all_players_from_all_groups: false # 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: diff --git a/src/main/java/net/william278/velocitab/config/Group.java b/src/main/java/net/william278/velocitab/config/Group.java index 1f3a975..4c8fadb 100644 --- a/src/main/java/net/william278/velocitab/config/Group.java +++ b/src/main/java/net/william278/velocitab/config/Group.java @@ -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.jetbrains.annotations.Nullable; import org.slf4j.event.Level; import java.util.List; @@ -65,7 +66,13 @@ public record Group( @NotNull public Set registeredServers(@NotNull Velocitab plugin) { - if (isDefault(plugin) && plugin.getSettings().isFallbackEnabled()) { + return registeredServers(plugin, true); + } + + @NotNull + public Set registeredServers(@NotNull Velocitab plugin, boolean includeAllPlayers) { + if ((includeAllPlayers && plugin.getSettings().isShowAllPlayersFromAllGroups()) || + (isDefault(plugin) && plugin.getSettings().isFallbackEnabled())) { return Sets.newHashSet(plugin.getServer().getAllServers()); } return getRegexServers(plugin); @@ -103,6 +110,9 @@ public record Group( @NotNull public Set getPlayers(@NotNull Velocitab plugin, @NotNull TabPlayer tabPlayer) { + if (plugin.getSettings().isShowAllPlayersFromAllGroups()) { + return Sets.newHashSet(plugin.getServer().getAllPlayers()); + } if (onlyListPlayersInSameServer) { return tabPlayer.getPlayer().getCurrentServer() .map(s -> Sets.newHashSet(s.getServer().getPlayersConnected())) @@ -111,8 +121,18 @@ public record Group( return getPlayers(plugin); } + /** + * Retrieves the set of TabPlayers associated with the given Velocitab plugin instance. + * If the plugin is configured to show all players from all groups, all players will be returned. + * + * @param plugin The Velocitab plugin instance. + * @return A set of TabPlayers. + */ @NotNull public Set getTabPlayers(@NotNull Velocitab plugin) { + if (plugin.getSettings().isShowAllPlayersFromAllGroups()) { + return Sets.newHashSet(plugin.getTabList().getPlayers().values()); + } return plugin.getTabList().getPlayers() .values() .stream() @@ -122,6 +142,9 @@ public record Group( @NotNull public Set getTabPlayers(@NotNull Velocitab plugin, @NotNull TabPlayer tabPlayer) { + if (plugin.getSettings().isShowAllPlayersFromAllGroups()) { + return Sets.newHashSet(plugin.getTabList().getPlayers().values()); + } if (onlyListPlayersInSameServer) { return plugin.getTabList().getPlayers() .values() @@ -133,7 +156,7 @@ public record Group( } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (!(obj instanceof Group group)) { return false; } diff --git a/src/main/java/net/william278/velocitab/config/Settings.java b/src/main/java/net/william278/velocitab/config/Settings.java index d2497f7..2616c3b 100644 --- a/src/main/java/net/william278/velocitab/config/Settings.java +++ b/src/main/java/net/william278/velocitab/config/Settings.java @@ -63,6 +63,9 @@ public class Settings implements ConfigValidator { @Comment("The formats to use for the fallback group.") private String fallbackGroup = "default"; + @Comment("Whether to show all players from all groups in the TAB list.") + private boolean showAllPlayersFromAllGroups = false; + @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 serverDisplayNames = Map.of("very-long-server-name", "VLSN"); diff --git a/src/main/java/net/william278/velocitab/config/TabGroups.java b/src/main/java/net/william278/velocitab/config/TabGroups.java index 9866b0d..254e63c 100644 --- a/src/main/java/net/william278/velocitab/config/TabGroups.java +++ b/src/main/java/net/william278/velocitab/config/TabGroups.java @@ -89,7 +89,7 @@ public class TabGroups implements ConfigValidator { throw new IllegalStateException("No default group found"); } for (Group group : groups) { - if (group.registeredServers(plugin) + if (group.registeredServers(plugin, false) .stream() .anyMatch(s -> s.getServerInfo().getName().equalsIgnoreCase(server))) { return group; diff --git a/src/main/java/net/william278/velocitab/sorting/SortingManager.java b/src/main/java/net/william278/velocitab/sorting/SortingManager.java index fc3f12f..d3c3576 100644 --- a/src/main/java/net/william278/velocitab/sorting/SortingManager.java +++ b/src/main/java/net/william278/velocitab/sorting/SortingManager.java @@ -24,17 +24,18 @@ import net.william278.velocitab.Velocitab; import net.william278.velocitab.config.Placeholder; import net.william278.velocitab.player.TabPlayer; import org.jetbrains.annotations.NotNull; -import org.slf4j.event.Level; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.regex.Pattern; import java.util.stream.Collectors; public class SortingManager { private final Velocitab plugin; private static final String DELIMITER = ":::"; + private static final Pattern NUMBER_PATTERN = Pattern.compile("^-?[0-9]\\d*(\\.\\d+)?$"); public SortingManager(@NotNull Velocitab plugin) { this.plugin = plugin; @@ -71,7 +72,7 @@ public class SortingManager { return ""; } - if (value.matches("^-?[0-9]\\d*(\\.\\d+)?$")) { + if (NUMBER_PATTERN.matcher(value).matches()) { double parsed = Double.parseDouble(value); parsed = Math.max(0, parsed); return compressNumber(Integer.MAX_VALUE / 4d - parsed); diff --git a/src/main/java/net/william278/velocitab/tab/GroupTasks.java b/src/main/java/net/william278/velocitab/tab/GroupTasks.java new file mode 100644 index 0000000..fc8879a --- /dev/null +++ b/src/main/java/net/william278/velocitab/tab/GroupTasks.java @@ -0,0 +1,39 @@ +/* + * This file is part of Velocitab, licensed under the Apache License 2.0. + * + * Copyright (c) William278 + * Copyright (c) contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.william278.velocitab.tab; + +import com.velocitypowered.api.scheduler.ScheduledTask; +import org.jetbrains.annotations.Nullable; + +public record GroupTasks(@Nullable ScheduledTask updateTask, @Nullable ScheduledTask headerFooterTask, @Nullable ScheduledTask latencyTask) { + + public void cancel() { + if (updateTask != null) { + updateTask.cancel(); + } + if (headerFooterTask != null) { + headerFooterTask.cancel(); + } + if (latencyTask != null) { + latencyTask.cancel(); + } + } + +} diff --git a/src/main/java/net/william278/velocitab/tab/PlayerTabList.java b/src/main/java/net/william278/velocitab/tab/PlayerTabList.java index 50c0f97..c8d88e5 100644 --- a/src/main/java/net/william278/velocitab/tab/PlayerTabList.java +++ b/src/main/java/net/william278/velocitab/tab/PlayerTabList.java @@ -25,7 +25,6 @@ import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.player.TabList; import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.proxy.server.RegisteredServer; -import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.scheduler.ScheduledTask; import lombok.AccessLevel; import lombok.Getter; @@ -36,7 +35,6 @@ 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; @@ -54,15 +52,13 @@ public class PlayerTabList { private final VanishTabList vanishTabList; @Getter(value = AccessLevel.PUBLIC) private final Map players; - private final Map placeholderTasks; - private final Map headerFooterTasks; + private final Map groupTasks; public PlayerTabList(@NotNull Velocitab plugin) { this.plugin = plugin; this.vanishTabList = new VanishTabList(plugin, this); this.players = Maps.newConcurrentMap(); - this.placeholderTasks = Maps.newConcurrentMap(); - this.headerFooterTasks = Maps.newConcurrentMap(); + this.groupTasks = Maps.newConcurrentMap(); this.reloadUpdate(); this.registerListener(); } @@ -104,7 +100,9 @@ public class PlayerTabList { final String serverName = server.get().getServerInfo().getName(); final Group group = getGroup(serverName); - final boolean isDefault = group.registeredServers(plugin).stream().noneMatch(s -> s.getServerInfo().getName().equals(serverName)); + final boolean isDefault = group.registeredServers(plugin) + .stream() + .noneMatch(s -> s.getServerInfo().getName().equals(serverName)); if (isDefault && !plugin.getSettings().isFallbackEnabled()) { return; @@ -119,10 +117,7 @@ public class PlayerTabList { * Removes the player's entry from the tab list of all other players on the same group servers. */ public void close() { - placeholderTasks.values().forEach(ScheduledTask::cancel); - placeholderTasks.clear(); - headerFooterTasks.values().forEach(ScheduledTask::cancel); - headerFooterTasks.clear(); + groupTasks.values().forEach(GroupTasks::cancel); plugin.getServer().getAllPlayers().forEach(p -> { final Optional server = p.getCurrentServer(); if (server.isEmpty()) return; @@ -345,10 +340,7 @@ public class PlayerTabList { player.getPlayer().getTabList().getEntries().stream() .filter(e -> e.getProfile().getId().equals(tabPlayer.getPlayer().getUniqueId())).findFirst() - .ifPresent(entry -> { - entry.setDisplayName(displayName); - entry.setLatency(Math.max((int) tabPlayer.getPlayer().getPing(), 0)); - }); + .ifPresent(entry -> entry.setDisplayName(displayName)); }); }); } @@ -375,26 +367,54 @@ public class PlayerTabList { } // Update the tab list periodically - private void updatePeriodically(Group group) { + private void updatePeriodically(@NotNull Group group) { cancelTasks(group); + ScheduledTask headerFooterTask = null; + ScheduledTask updateTask = null; + ScheduledTask latencyTask; + if (group.headerFooterUpdateRate() > 0) { - final ScheduledTask headerFooterTask = plugin.getServer().getScheduler() + headerFooterTask = plugin.getServer().getScheduler() .buildTask(plugin, () -> updateGroupPlayers(group, false, true)) .delay(1, TimeUnit.SECONDS) .repeat(Math.max(200, group.headerFooterUpdateRate()), TimeUnit.MILLISECONDS) .schedule(); - headerFooterTasks.put(group, headerFooterTask); } if (group.placeholderUpdateRate() > 0) { - final ScheduledTask updateTask = plugin.getServer().getScheduler() + updateTask = plugin.getServer().getScheduler() .buildTask(plugin, () -> updateGroupPlayers(group, true, false)) .delay(1, TimeUnit.SECONDS) .repeat(Math.max(200, group.placeholderUpdateRate()), TimeUnit.MILLISECONDS) .schedule(); - placeholderTasks.put(group, updateTask); } + + latencyTask = plugin.getServer().getScheduler() + .buildTask(plugin, () -> updateLatency(group)) + .delay(1, TimeUnit.SECONDS) + .repeat(3, TimeUnit.SECONDS) + .schedule(); + + groupTasks.put(group, new GroupTasks(headerFooterTask, updateTask, latencyTask)); + } + + private void updateLatency(@NotNull Group group) { + final Set groupPlayers = group.getTabPlayers(plugin); + if (groupPlayers.isEmpty()) { + return; + } + groupPlayers.stream() + .filter(player -> player.getPlayer().isActive()) + .forEach(player -> { + final int latency = (int) player.getPlayer().getPing(); + final Set players = group.getTabPlayers(plugin, player); + players.forEach(p -> { + p.getPlayer().getTabList().getEntries().stream() + .filter(e -> e.getProfile().getId().equals(player.getPlayer().getUniqueId())).findFirst() + .ifPresent(entry -> entry.setLatency(Math.max(latency, 0))); + }); + }); } /** @@ -425,25 +445,11 @@ public class PlayerTabList { } } - private void cancelTasks(Group group) { - ScheduledTask task = placeholderTasks.entrySet().stream() - .filter(entry -> entry.getKey().equals(group)) - .map(Map.Entry::getValue) - .findFirst() - .orElse(null); - if (task != null) { - task.cancel(); - placeholderTasks.remove(group); - } - - task = headerFooterTasks.entrySet().stream() - .filter(entry -> entry.getKey().equals(group)) - .map(Map.Entry::getValue) - .findFirst() - .orElse(null); - if (task != null) { - task.cancel(); - headerFooterTasks.remove(group); + private void cancelTasks(@NotNull Group group) { + final GroupTasks tasks = groupTasks.get(group); + if (tasks != null) { + tasks.cancel(); + groupTasks.remove(group); } } @@ -451,8 +457,6 @@ public class PlayerTabList { * Update the TAB list for all players when a plugin or proxy reload is performed */ public void reloadUpdate() { - placeholderTasks.values().forEach(ScheduledTask::cancel); - placeholderTasks.clear(); plugin.getTabGroups().getGroups().forEach(this::updatePeriodically); if (players.isEmpty()) {