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.
This commit is contained in:
AlexDev_ 2023-11-05 21:15:23 +01:00 committed by GitHub
parent c82a0c75d8
commit c36e17b75e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 146 additions and 49 deletions

View File

@ -59,4 +59,20 @@ You can also use `VelocitabAPI#getCustomPlayerName` which accepts a Velocity `Pl
// Getting a player's custom name // Getting a player's custom name
Optional<String> customName = velocitabAPI.getCustomPlayerName(player); Optional<String> customName = velocitabAPI.getCustomPlayerName(player);
``` ```
</details>
## 3. Listening to PlayerAddedToTabEvent
You can listen to `PlayerAddedToTabEvent` to get notified when a player is added to a group TabList.
<details>
<summary>Example &mdash; Listening to PlayerAddToTabEvent</summary>
```java
@Subscribe
public void onPlayerAddToTab(PlayerAddToTabEvent event) {
VelocitabAPI velocitabAPI = VelocitabAPI.getInstance();
velocitabAPI.setCustomPlayerName(event.getPlayer().getPlayer(), "CustomName");
}
```
</details> </details>

View File

@ -9,7 +9,7 @@ The Velocitab API shares version numbering with the plugin itself for consistenc
| API Version | Velocitab Versions | Supported | | API Version | Velocitab Versions | Supported |
|:-----------:|:----------------------:|:---------:| |:-----------:|:----------------------:|:---------:|
| v1.x | _v1.5.1&mdash;Current_ | ✅ | | v1.x | _v1.5.2&mdash;Current_ | ✅ |
## Table of contents ## Table of contents
1. Adding the API to your project 1. Adding the API to your project

View File

@ -3,6 +3,6 @@ javaVersion=16
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.5.1 plugin_version=1.5.2
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

@ -59,6 +59,7 @@ import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Plugin(id = "velocitab") @Plugin(id = "velocitab")
public class Velocitab { public class Velocitab {
@ -89,10 +90,10 @@ public class Velocitab {
public void onProxyInitialization(@NotNull ProxyInitializeEvent event) { public void onProxyInitialization(@NotNull ProxyInitializeEvent event) {
loadSettings(); loadSettings();
loadHooks(); loadHooks();
prepareVanishManager();
prepareScoreboardManager(); prepareScoreboardManager();
prepareTabList(); prepareTabList();
prepareSortingManager(); prepareSortingManager();
prepareVanishManager();
registerCommands(); registerCommands();
registerMetrics(); registerMetrics();
checkForUpdates(); checkForUpdates();
@ -104,6 +105,7 @@ public class Velocitab {
public void onProxyShutdown(@NotNull ProxyShutdownEvent event) { public void onProxyShutdown(@NotNull ProxyShutdownEvent event) {
server.getScheduler().tasksByPlugin(this).forEach(ScheduledTask::cancel); server.getScheduler().tasksByPlugin(this).forEach(ScheduledTask::cancel);
disableScoreboardManager(); disableScoreboardManager();
disableTabList();
getLuckPermsHook().ifPresent(LuckPermsHook::close); getLuckPermsHook().ifPresent(LuckPermsHook::close);
VelocitabAPI.unregister(); VelocitabAPI.unregister();
logger.info("Successfully disabled Velocitab"); logger.info("Successfully disabled Velocitab");
@ -174,10 +176,17 @@ public class Velocitab {
private void disableScoreboardManager() { private void disableScoreboardManager() {
if (scoreboardManager != null && settings.isSendScoreboardPackets()) { if (scoreboardManager != null && settings.isSendScoreboardPackets()) {
scoreboardManager.close();
scoreboardManager.unregisterPacket(); scoreboardManager.unregisterPacket();
} }
} }
private void disableTabList() {
if (tabList != null) {
tabList.close();
}
}
private void prepareVanishManager() { private void prepareVanishManager() {
this.vanishManager = new VanishManager(this); this.vanishManager = new VanishManager(this);
} }
@ -204,10 +213,12 @@ public class Velocitab {
private void prepareTabList() { private void prepareTabList() {
this.tabList = new PlayerTabList(this); this.tabList = new PlayerTabList(this);
server.getEventManager().register(this, tabList); server.getEventManager().register(this, tabList);
server.getScheduler().buildTask(this, tabList::load).delay(1, TimeUnit.SECONDS).schedule();
} }
private void prepareAPI() { private void prepareAPI() {
VelocitabAPI.register(this); VelocitabAPI.register(this);
} }
private void registerCommands() { private void registerCommands() {

View File

@ -0,0 +1,28 @@
/*
* This file is part of Velocitab, licensed under the Apache License 2.0.
*
* Copyright (c) William278 <will27528@gmail.com>
* 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<String> groupServers) {
}

View File

@ -43,7 +43,7 @@ public enum Placeholder {
.orElse("")), .orElse("")),
CURRENT_DATE((plugin, player) -> DateTimeFormatter.ofPattern("dd MMM yyyy").format(LocalDateTime.now())), CURRENT_DATE((plugin, player) -> DateTimeFormatter.ofPattern("dd MMM yyyy").format(LocalDateTime.now())),
CURRENT_TIME((plugin, player) -> DateTimeFormatter.ofPattern("HH:mm:ss").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)), SERVER((plugin, player) -> player.getServerDisplayName(plugin)),
PING((plugin, player) -> Long.toString(player.getPlayer().getPing())), PING((plugin, player) -> Long.toString(player.getPlayer().getPing())),
PREFIX((plugin, player) -> player.getRole().getPrefix().orElse("")), PREFIX((plugin, player) -> player.getRole().getPrefix().orElse("")),

View File

@ -67,6 +67,10 @@ public class ScoreboardManager {
.orElseThrow(() -> new IllegalArgumentException("No adapter found for protocol version " + version)); .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) { public void resetCache(@NotNull Player player) {
final String team = createdTeams.remove(player.getUniqueId()); final String team = createdTeams.remove(player.getUniqueId());
if (team != null) { if (team != null) {
@ -92,10 +96,10 @@ public class ScoreboardManager {
return; return;
} }
UpdateTeamsPacket packet = UpdateTeamsPacket.removeTeam(plugin, createdTeams.get(player.getUniqueId())); final UpdateTeamsPacket packet = UpdateTeamsPacket.removeTeam(plugin, teamName);
siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> { 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()); || plugin.getVanishManager().canSee(player.getUsername(), player.getUsername());
if (!canSee) { if (!canSee) {
@ -145,6 +149,7 @@ public class ScoreboardManager {
final String name = player.getUsername(); final String name = player.getUsername();
final TabPlayer tabPlayer = plugin.getTabList().getTabPlayer(player).orElseThrow(); final TabPlayer tabPlayer = plugin.getTabList().getTabPlayer(player).orElseThrow();
tabPlayer.getNametag(plugin).thenAccept(nametag -> { tabPlayer.getNametag(plugin).thenAccept(nametag -> {
final String[] split = nametag.split(player.getUsername(), 2); final String[] split = nametag.split(player.getUsername(), 2);
final String prefix = split[0]; final String prefix = split[0];

View File

@ -32,15 +32,18 @@ import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.scheduler.ScheduledTask; import com.velocitypowered.api.scheduler.ScheduledTask;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.william278.velocitab.Velocitab; import net.william278.velocitab.Velocitab;
import net.william278.velocitab.api.PlayerAddedToTabEvent;
import net.william278.velocitab.config.Placeholder; import net.william278.velocitab.config.Placeholder;
import net.william278.velocitab.player.Role; import net.william278.velocitab.player.Role;
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.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -49,13 +52,13 @@ import java.util.concurrent.TimeUnit;
*/ */
public class PlayerTabList { public class PlayerTabList {
private final Velocitab plugin; private final Velocitab plugin;
private final ConcurrentLinkedQueue<TabPlayer> players; private final ConcurrentHashMap<UUID, TabPlayer> players;
private final ConcurrentLinkedQueue<String> fallbackServers; private final ConcurrentLinkedQueue<String> fallbackServers;
private ScheduledTask updateTask; private ScheduledTask updateTask;
public PlayerTabList(@NotNull Velocitab plugin) { public PlayerTabList(@NotNull Velocitab plugin) {
this.plugin = plugin; this.plugin = plugin;
this.players = new ConcurrentLinkedQueue<>(); this.players = new ConcurrentHashMap<>();
this.fallbackServers = new ConcurrentLinkedQueue<>(); this.fallbackServers = new ConcurrentLinkedQueue<>();
// If the update time is set to 0 do not schedule the updater // 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<TabPlayer> getTabPlayer(@NotNull Player player) { public Optional<TabPlayer> 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<ServerConnection> server = p.getCurrentServer();
if (server.isEmpty()) return;
final List<RegisteredServer> 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<ServerConnection> server = p.getCurrentServer();
if (server.isEmpty()) return;
final List<RegisteredServer> 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") @SuppressWarnings("UnstableApiUsage")
@ -88,22 +134,27 @@ public class PlayerTabList {
if (serversInGroup.isEmpty() && if (serversInGroup.isEmpty() &&
(previousServer != null && !this.fallbackServers.contains(previousServer.getServerInfo().getName()))) { (previousServer != null && !this.fallbackServers.contains(previousServer.getServerInfo().getName()))) {
event.getPlayer().sendPlayerListHeaderAndFooter(Component.empty(), Component.empty()); event.getPlayer().sendPlayerListHeaderAndFooter(Component.empty(), Component.empty());
players.remove(event.getPlayer().getUniqueId());
return; return;
} }
joinPlayer(joined, serversInGroup.orElseGet(ArrayList::new));
}
private void joinPlayer(@NotNull Player joined, @NotNull List<String> serversInGroup) {
// Add the player to the tracking list if they are not already listed // Add the player to the tracking list if they are not already listed
final TabPlayer tabPlayer = getTabPlayer(joined).orElseGet(() -> createTabPlayer(joined)); final TabPlayer tabPlayer = getTabPlayer(joined).orElseGet(() -> createTabPlayer(joined));
players.add(tabPlayer); players.putIfAbsent(joined.getUniqueId(), tabPlayer);
final boolean isVanished = plugin.getVanishManager().isVanished(joined.getUsername()); final boolean isVanished = plugin.getVanishManager().isVanished(joined.getUsername());
// Update lists // Update lists
plugin.getServer().getScheduler() plugin.getServer().getScheduler()
.buildTask(plugin, () -> { .buildTask(plugin, () -> {
final TabList tabList = joined.getTabList(); 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 // Skip players on other servers if the setting is enabled
if (plugin.getSettings().isOnlyListPlayersInSameGroup() && serversInGroup.isPresent() if (plugin.getSettings().isOnlyListPlayersInSameGroup()
&& !serversInGroup.get().contains(player.getServerName())) { && !serversInGroup.contains(player.getServerName())) {
continue; continue;
} }
// check if current player can see the joined player // 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) { !plugin.getVanishManager().canSee(joined.getUsername(), player.getPlayer().getUsername())) && player.getPlayer() != joined) {
tabList.removeEntry(player.getPlayer().getUniqueId()); tabList.removeEntry(player.getPlayer().getUniqueId());
} else { } else {
tabList.getEntries().stream() tabList.getEntry(player.getPlayer().getUniqueId()).ifPresentOrElse(
.filter(e -> e.getProfile().getId().equals(player.getPlayer().getUniqueId())).findFirst() entry -> player.getDisplayName(plugin).thenAccept(entry::setDisplayName),
.ifPresentOrElse( () -> createEntry(player, tabList).thenAccept(tabList::addEntry)
entry -> player.getDisplayName(plugin).thenAccept(entry::setDisplayName), );
() -> createEntry(player, tabList).thenAccept(tabList::addEntry)
);
} }
player.sendHeaderAndFooter(this); player.sendHeaderAndFooter(this);
@ -132,6 +181,9 @@ public class PlayerTabList {
s.resendAllTeams(joined); s.resendAllTeams(joined);
tabPlayer.getTeamName(plugin).thenAccept(t -> s.updateRole(joined, t)); 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) .delay(500, TimeUnit.MILLISECONDS)
.schedule(); .schedule();
@ -147,9 +199,8 @@ public class PlayerTabList {
.build()); .build());
} }
@NotNull private void addEntry(@NotNull TabPlayer player, @NotNull TabList tabList, @NotNull Component displayName) {
private TabListEntry createEntry(@NotNull TabPlayer player, @NotNull TabList tabList, Component displayName) { TabListEntry.builder()
return TabListEntry.builder()
.profile(player.getPlayer().getGameProfile()) .profile(player.getPlayer().getGameProfile())
.displayName(displayName) .displayName(displayName)
.latency(0) .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 @Subscribe
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) {
@ -195,9 +232,9 @@ public class PlayerTabList {
// Remove the player from the tracking list, Print warning if player was not removed // Remove the player from the tracking list, Print warning if player was not removed
final UUID uuid = event.getPlayer().getUniqueId(); 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)", 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 // 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 // Update the tab list of all players
plugin.getServer().getScheduler() plugin.getServer().getScheduler()
.buildTask(plugin, () -> players.forEach(player -> { .buildTask(plugin, () -> players.values().forEach(player -> {
player.getPlayer().getTabList().removeEntry(uuid); player.getPlayer().getTabList().removeEntry(uuid);
player.sendHeaderAndFooter(this); player.sendHeaderAndFooter(this);
})) }))
@ -247,9 +284,9 @@ public class PlayerTabList {
return; 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())) { if (isVanished && !plugin.getVanishManager().canSee(player.getPlayer().getUsername(), tabPlayer.getPlayer().getUsername())) {
return; return;
} }
@ -263,7 +300,7 @@ public class PlayerTabList {
// Update the display names of all listed players // Update the display names of all listed players
public void updateDisplayNames() { public void updateDisplayNames() {
players.forEach(this::updatePlayerDisplayName); players.values().forEach(this::updatePlayerDisplayName);
} }
// Get the component for the TAB list header // Get the component for the TAB list header
@ -291,7 +328,7 @@ public class PlayerTabList {
if (players.isEmpty()) { if (players.isEmpty()) {
return; return;
} }
players.forEach(player -> { players.values().forEach(player -> {
this.updatePlayer(player); this.updatePlayer(player);
player.sendHeaderAndFooter(this); player.sendHeaderAndFooter(this);
}); });
@ -316,7 +353,7 @@ public class PlayerTabList {
if (plugin.getSettings().getUpdateRate() > 0) { if (plugin.getSettings().getUpdateRate() > 0) {
this.updatePeriodically(plugin.getSettings().getUpdateRate()); this.updatePeriodically(plugin.getSettings().getUpdateRate());
} else { } else {
players.forEach(player -> { players.values().forEach(player -> {
this.updatePlayer(player); this.updatePlayer(player);
player.sendHeaderAndFooter(this); player.sendHeaderAndFooter(this);
}); });
@ -382,11 +419,11 @@ public class PlayerTabList {
* @param player The player to remove * @param player The player to remove
*/ */
public void removeOfflinePlayer(@NotNull Player player) { 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) { public void vanishPlayer(@NotNull TabPlayer tabPlayer) {
players.forEach(p -> { players.values().forEach(p -> {
if (p.getPlayer().equals(tabPlayer.getPlayer())) { if (p.getPlayer().equals(tabPlayer.getPlayer())) {
return; return;
} }
@ -401,13 +438,13 @@ public class PlayerTabList {
final UUID uuid = tabPlayer.getPlayer().getUniqueId(); final UUID uuid = tabPlayer.getPlayer().getUniqueId();
tabPlayer.getDisplayName(plugin).thenAccept(c -> { tabPlayer.getDisplayName(plugin).thenAccept(c -> {
players.forEach(p -> { players.values().forEach(p -> {
if (p.getPlayer().equals(tabPlayer.getPlayer())) { if (p.getPlayer().equals(tabPlayer.getPlayer())) {
return; return;
} }
if (!p.getPlayer().getTabList().containsEntry(uuid)) { if (!p.getPlayer().getTabList().containsEntry(uuid)) {
createEntry(tabPlayer, p.getPlayer().getTabList(), c); addEntry(tabPlayer, p.getPlayer().getTabList(), c);
} }
}); });
}); });