From c36e17b75eaa9414f5b70139968f32ec72af563d Mon Sep 17 00:00:00 2001 From: AlexDev_ <56083016+alexdev03@users.noreply.github.com> Date: Sun, 5 Nov 2023 21:15:23 +0100 Subject: [PATCH] Added PlayerAddedToTabEvent, improved PlayerTabList performance and more (#114) Added PlayerAddedToTabEvent, improved PlayerTabList performance and added the possibility to reload the plugin without breaking the tab list. This is only for dev purposes. Bumped version to 1.5.2 Fixed a few problems. --- docs/API-Examples.md | 16 +++ docs/API.md | 2 +- gradle.properties | 2 +- .../net/william278/velocitab/Velocitab.java | 15 ++- .../velocitab/api/PlayerAddedToTabEvent.java | 28 ++++ .../velocitab/config/Placeholder.java | 2 +- .../velocitab/packet/ScoreboardManager.java | 9 +- .../velocitab/tab/PlayerTabList.java | 121 ++++++++++++------ 8 files changed, 146 insertions(+), 49 deletions(-) create mode 100644 src/main/java/net/william278/velocitab/api/PlayerAddedToTabEvent.java diff --git a/docs/API-Examples.md b/docs/API-Examples.md index 961d279..10c5b99 100644 --- a/docs/API-Examples.md +++ b/docs/API-Examples.md @@ -59,4 +59,20 @@ You can also use `VelocitabAPI#getCustomPlayerName` which accepts a Velocity `Pl // Getting a player's custom name Optional customName = velocitabAPI.getCustomPlayerName(player); ``` + + +## 3. Listening to PlayerAddedToTabEvent +You can listen to `PlayerAddedToTabEvent` to get notified when a player is added to a group TabList. + +
+Example — Listening to PlayerAddToTabEvent + +```java +@Subscribe +public void onPlayerAddToTab(PlayerAddToTabEvent event) { + VelocitabAPI velocitabAPI = VelocitabAPI.getInstance(); + velocitabAPI.setCustomPlayerName(event.getPlayer().getPlayer(), "CustomName"); +} +``` +
\ No newline at end of file diff --git a/docs/API.md b/docs/API.md index 71591fe..f2b0ea7 100644 --- a/docs/API.md +++ b/docs/API.md @@ -9,7 +9,7 @@ The Velocitab API shares version numbering with the plugin itself for consistenc | API Version | Velocitab Versions | Supported | |:-----------:|:----------------------:|:---------:| -| v1.x | _v1.5.1—Current_ | ✅ | +| v1.x | _v1.5.2—Current_ | ✅ | ## Table of contents 1. Adding the API to your project diff --git a/gradle.properties b/gradle.properties index 3f4ba36..d0d5e93 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,6 +3,6 @@ javaVersion=16 org.gradle.jvmargs='-Dfile.encoding=UTF-8' org.gradle.daemon=true -plugin_version=1.5.1 +plugin_version=1.5.2 plugin_archive=velocitab plugin_description=A beautiful and versatile TAB list plugin for Velocity proxies diff --git a/src/main/java/net/william278/velocitab/Velocitab.java b/src/main/java/net/william278/velocitab/Velocitab.java index edf81ca..bd67e88 100644 --- a/src/main/java/net/william278/velocitab/Velocitab.java +++ b/src/main/java/net/william278/velocitab/Velocitab.java @@ -59,6 +59,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.TimeUnit; @Plugin(id = "velocitab") public class Velocitab { @@ -89,10 +90,10 @@ public class Velocitab { public void onProxyInitialization(@NotNull ProxyInitializeEvent event) { loadSettings(); loadHooks(); + prepareVanishManager(); prepareScoreboardManager(); prepareTabList(); prepareSortingManager(); - prepareVanishManager(); registerCommands(); registerMetrics(); checkForUpdates(); @@ -104,6 +105,7 @@ public class Velocitab { public void onProxyShutdown(@NotNull ProxyShutdownEvent event) { server.getScheduler().tasksByPlugin(this).forEach(ScheduledTask::cancel); disableScoreboardManager(); + disableTabList(); getLuckPermsHook().ifPresent(LuckPermsHook::close); VelocitabAPI.unregister(); logger.info("Successfully disabled Velocitab"); @@ -174,10 +176,17 @@ public class Velocitab { private void disableScoreboardManager() { if (scoreboardManager != null && settings.isSendScoreboardPackets()) { + scoreboardManager.close(); scoreboardManager.unregisterPacket(); } } + private void disableTabList() { + if (tabList != null) { + tabList.close(); + } + } + private void prepareVanishManager() { this.vanishManager = new VanishManager(this); } @@ -204,10 +213,12 @@ public class Velocitab { private void prepareTabList() { this.tabList = new PlayerTabList(this); server.getEventManager().register(this, tabList); + + server.getScheduler().buildTask(this, tabList::load).delay(1, TimeUnit.SECONDS).schedule(); } private void prepareAPI() { - VelocitabAPI.register(this); + VelocitabAPI.register(this); } private void registerCommands() { diff --git a/src/main/java/net/william278/velocitab/api/PlayerAddedToTabEvent.java b/src/main/java/net/william278/velocitab/api/PlayerAddedToTabEvent.java new file mode 100644 index 0000000..72763a7 --- /dev/null +++ b/src/main/java/net/william278/velocitab/api/PlayerAddedToTabEvent.java @@ -0,0 +1,28 @@ +/* + * 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.api; + +import net.william278.velocitab.player.TabPlayer; + +import java.util.List; + +public record PlayerAddedToTabEvent(TabPlayer player, String group, List groupServers) { + +} diff --git a/src/main/java/net/william278/velocitab/config/Placeholder.java b/src/main/java/net/william278/velocitab/config/Placeholder.java index 0014f08..42dc257 100644 --- a/src/main/java/net/william278/velocitab/config/Placeholder.java +++ b/src/main/java/net/william278/velocitab/config/Placeholder.java @@ -43,7 +43,7 @@ public enum Placeholder { .orElse("")), CURRENT_DATE((plugin, player) -> DateTimeFormatter.ofPattern("dd MMM yyyy").format(LocalDateTime.now())), CURRENT_TIME((plugin, player) -> DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now())), - USERNAME((plugin, player) -> plugin.getFormatter().escape(player.getCustomName().orElse(player.getPlayer().getUsername()))), + USERNAME((plugin, player) -> player.getCustomName().orElse(player.getPlayer().getUsername())), SERVER((plugin, player) -> player.getServerDisplayName(plugin)), PING((plugin, player) -> Long.toString(player.getPlayer().getPing())), PREFIX((plugin, player) -> player.getRole().getPrefix().orElse("")), diff --git a/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java b/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java index bcbeb25..5588a18 100644 --- a/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java +++ b/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java @@ -67,6 +67,10 @@ public class ScoreboardManager { .orElseThrow(() -> new IllegalArgumentException("No adapter found for protocol version " + version)); } + public void close() { + plugin.getServer().getAllPlayers().forEach(this::resetCache); + } + public void resetCache(@NotNull Player player) { final String team = createdTeams.remove(player.getUniqueId()); if (team != null) { @@ -92,10 +96,10 @@ public class ScoreboardManager { return; } - UpdateTeamsPacket packet = UpdateTeamsPacket.removeTeam(plugin, createdTeams.get(player.getUniqueId())); + final UpdateTeamsPacket packet = UpdateTeamsPacket.removeTeam(plugin, teamName); siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> { - boolean canSee = !plugin.getVanishManager().isVanished(connected.getUsername()) + final boolean canSee = !plugin.getVanishManager().isVanished(connected.getUsername()) || plugin.getVanishManager().canSee(player.getUsername(), player.getUsername()); if (!canSee) { @@ -145,6 +149,7 @@ public class ScoreboardManager { final String name = player.getUsername(); final TabPlayer tabPlayer = plugin.getTabList().getTabPlayer(player).orElseThrow(); + tabPlayer.getNametag(plugin).thenAccept(nametag -> { final String[] split = nametag.split(player.getUsername(), 2); final String prefix = split[0]; diff --git a/src/main/java/net/william278/velocitab/tab/PlayerTabList.java b/src/main/java/net/william278/velocitab/tab/PlayerTabList.java index 941c96b..fd4891c 100644 --- a/src/main/java/net/william278/velocitab/tab/PlayerTabList.java +++ b/src/main/java/net/william278/velocitab/tab/PlayerTabList.java @@ -32,15 +32,18 @@ import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.scheduler.ScheduledTask; import net.kyori.adventure.text.Component; import net.william278.velocitab.Velocitab; +import net.william278.velocitab.api.PlayerAddedToTabEvent; import net.william278.velocitab.config.Placeholder; import net.william278.velocitab.player.Role; import net.william278.velocitab.player.TabPlayer; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; @@ -49,13 +52,13 @@ import java.util.concurrent.TimeUnit; */ public class PlayerTabList { private final Velocitab plugin; - private final ConcurrentLinkedQueue players; + private final ConcurrentHashMap players; private final ConcurrentLinkedQueue fallbackServers; private ScheduledTask updateTask; public PlayerTabList(@NotNull Velocitab plugin) { this.plugin = plugin; - this.players = new ConcurrentLinkedQueue<>(); + this.players = new ConcurrentHashMap<>(); this.fallbackServers = new ConcurrentLinkedQueue<>(); // If the update time is set to 0 do not schedule the updater @@ -64,8 +67,51 @@ public class PlayerTabList { } } + /** + * Retrieves a TabPlayer object corresponding to the given Player object. + * + * @param player The Player object for which to retrieve the corresponding TabPlayer. + * @return An Optional object containing the TabPlayer if found, or an empty Optional if not found. + */ public Optional getTabPlayer(@NotNull Player player) { - return players.stream().filter(tabPlayer -> tabPlayer.getPlayer().getUniqueId().equals(player.getUniqueId())).findFirst(); + return Optional.ofNullable(players.get(player.getUniqueId())); + } + + + /** + * Loads the tab list for all players connected to the server. + * Removes the player's entry from the tab list of all other players on the same group servers. + */ + public void load() { + plugin.getServer().getAllPlayers().forEach(p -> { + final Optional server = p.getCurrentServer(); + if (server.isEmpty()) return; + + final List serversInGroup = new ArrayList<>(getGroupServers(server.get().getServerInfo().getName())); + if (serversInGroup.isEmpty()) return; + + serversInGroup.remove(server.get().getServer()); + + joinPlayer(p, serversInGroup.stream().map(s -> s.getServerInfo().getName()).toList()); + }); + } + + /** + * Closes the tab list for all players connected to the server. + * Removes the player's entry from the tab list of all other players on the same group servers. + */ + public void close() { + plugin.getServer().getAllPlayers().forEach(p -> { + final Optional server = p.getCurrentServer(); + if (server.isEmpty()) return; + + final List serversInGroup = new ArrayList<>(getGroupServers(server.get().getServerInfo().getName())); + if (serversInGroup.isEmpty()) return; + + serversInGroup.remove(server.get().getServer()); + + serversInGroup.forEach(s -> s.getPlayersConnected().forEach(t -> t.getTabList().removeEntry(p.getUniqueId()))); + }); } @SuppressWarnings("UnstableApiUsage") @@ -88,22 +134,27 @@ public class PlayerTabList { if (serversInGroup.isEmpty() && (previousServer != null && !this.fallbackServers.contains(previousServer.getServerInfo().getName()))) { event.getPlayer().sendPlayerListHeaderAndFooter(Component.empty(), Component.empty()); + players.remove(event.getPlayer().getUniqueId()); return; } + joinPlayer(joined, serversInGroup.orElseGet(ArrayList::new)); + } + + private void joinPlayer(@NotNull Player joined, @NotNull List serversInGroup) { // Add the player to the tracking list if they are not already listed final TabPlayer tabPlayer = getTabPlayer(joined).orElseGet(() -> createTabPlayer(joined)); - players.add(tabPlayer); + players.putIfAbsent(joined.getUniqueId(), tabPlayer); final boolean isVanished = plugin.getVanishManager().isVanished(joined.getUsername()); // Update lists plugin.getServer().getScheduler() .buildTask(plugin, () -> { final TabList tabList = joined.getTabList(); - for (TabPlayer player : players) { + for (final TabPlayer player : players.values()) { // Skip players on other servers if the setting is enabled - if (plugin.getSettings().isOnlyListPlayersInSameGroup() && serversInGroup.isPresent() - && !serversInGroup.get().contains(player.getServerName())) { + if (plugin.getSettings().isOnlyListPlayersInSameGroup() + && !serversInGroup.contains(player.getServerName())) { continue; } // check if current player can see the joined player @@ -117,12 +168,10 @@ public class PlayerTabList { !plugin.getVanishManager().canSee(joined.getUsername(), player.getPlayer().getUsername())) && player.getPlayer() != joined) { tabList.removeEntry(player.getPlayer().getUniqueId()); } else { - tabList.getEntries().stream() - .filter(e -> e.getProfile().getId().equals(player.getPlayer().getUniqueId())).findFirst() - .ifPresentOrElse( - entry -> player.getDisplayName(plugin).thenAccept(entry::setDisplayName), - () -> createEntry(player, tabList).thenAccept(tabList::addEntry) - ); + tabList.getEntry(player.getPlayer().getUniqueId()).ifPresentOrElse( + entry -> player.getDisplayName(plugin).thenAccept(entry::setDisplayName), + () -> createEntry(player, tabList).thenAccept(tabList::addEntry) + ); } player.sendHeaderAndFooter(this); @@ -132,6 +181,9 @@ public class PlayerTabList { s.resendAllTeams(joined); tabPlayer.getTeamName(plugin).thenAccept(t -> s.updateRole(joined, t)); }); + + // Fire event without listening for result + plugin.getServer().getEventManager().fireAndForget(new PlayerAddedToTabEvent(tabPlayer, tabPlayer.getServerGroup(plugin), serversInGroup)); }) .delay(500, TimeUnit.MILLISECONDS) .schedule(); @@ -147,9 +199,8 @@ public class PlayerTabList { .build()); } - @NotNull - private TabListEntry createEntry(@NotNull TabPlayer player, @NotNull TabList tabList, Component displayName) { - return TabListEntry.builder() + private void addEntry(@NotNull TabPlayer player, @NotNull TabList tabList, @NotNull Component displayName) { + TabListEntry.builder() .profile(player.getPlayer().getGameProfile()) .displayName(displayName) .latency(0) @@ -173,20 +224,6 @@ public class PlayerTabList { } - private void addPlayerToTabList(@NotNull TabPlayer player, @NotNull TabPlayer newPlayer, TabListEntry entry) { - if (newPlayer.getPlayer().getUniqueId().equals(player.getPlayer().getUniqueId())) { - return; - } - - final boolean isPresent = player.getPlayer() - .getTabList().getEntries().stream() - .noneMatch(e -> e.getProfile().getId().equals(newPlayer.getPlayer().getUniqueId())); - - if (isPresent) { - player.getPlayer().getTabList().addEntry(entry); - } - } - @Subscribe public void onPlayerQuit(@NotNull DisconnectEvent event) { if (event.getLoginStatus() != DisconnectEvent.LoginStatus.SUCCESSFUL_LOGIN) { @@ -195,9 +232,9 @@ public class PlayerTabList { // Remove the player from the tracking list, Print warning if player was not removed final UUID uuid = event.getPlayer().getUniqueId(); - if (!players.removeIf(listed -> listed.getPlayer().getUniqueId().equals(uuid))) { + if (players.remove(uuid) == null) { plugin.log(String.format("Failed to remove disconnecting player %s (UUID: %s)", - event.getPlayer().getUsername(), uuid.toString())); + event.getPlayer().getUsername(), uuid)); } // Remove the player from the tab list of all other players @@ -205,7 +242,7 @@ public class PlayerTabList { // Update the tab list of all players plugin.getServer().getScheduler() - .buildTask(plugin, () -> players.forEach(player -> { + .buildTask(plugin, () -> players.values().forEach(player -> { player.getPlayer().getTabList().removeEntry(uuid); player.sendHeaderAndFooter(this); })) @@ -247,9 +284,9 @@ public class PlayerTabList { return; } - boolean isVanished = plugin.getVanishManager().isVanished(tabPlayer.getPlayer().getUsername()); + final boolean isVanished = plugin.getVanishManager().isVanished(tabPlayer.getPlayer().getUsername()); - players.forEach(player -> { + players.values().forEach(player -> { if (isVanished && !plugin.getVanishManager().canSee(player.getPlayer().getUsername(), tabPlayer.getPlayer().getUsername())) { return; } @@ -263,7 +300,7 @@ public class PlayerTabList { // Update the display names of all listed players public void updateDisplayNames() { - players.forEach(this::updatePlayerDisplayName); + players.values().forEach(this::updatePlayerDisplayName); } // Get the component for the TAB list header @@ -291,7 +328,7 @@ public class PlayerTabList { if (players.isEmpty()) { return; } - players.forEach(player -> { + players.values().forEach(player -> { this.updatePlayer(player); player.sendHeaderAndFooter(this); }); @@ -316,7 +353,7 @@ public class PlayerTabList { if (plugin.getSettings().getUpdateRate() > 0) { this.updatePeriodically(plugin.getSettings().getUpdateRate()); } else { - players.forEach(player -> { + players.values().forEach(player -> { this.updatePlayer(player); player.sendHeaderAndFooter(this); }); @@ -382,11 +419,11 @@ public class PlayerTabList { * @param player The player to remove */ public void removeOfflinePlayer(@NotNull Player player) { - players.removeIf(tabPlayer -> tabPlayer.getPlayer().getUniqueId().equals(player.getUniqueId())); + players.remove(player.getUniqueId()); } public void vanishPlayer(@NotNull TabPlayer tabPlayer) { - players.forEach(p -> { + players.values().forEach(p -> { if (p.getPlayer().equals(tabPlayer.getPlayer())) { return; } @@ -401,13 +438,13 @@ public class PlayerTabList { final UUID uuid = tabPlayer.getPlayer().getUniqueId(); tabPlayer.getDisplayName(plugin).thenAccept(c -> { - players.forEach(p -> { + players.values().forEach(p -> { if (p.getPlayer().equals(tabPlayer.getPlayer())) { return; } if (!p.getPlayer().getTabList().containsEntry(uuid)) { - createEntry(tabPlayer, p.getPlayer().getTabList(), c); + addEntry(tabPlayer, p.getPlayer().getTabList(), c); } }); });