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

@ -60,3 +60,19 @@ You can also use `VelocitabAPI#getCustomPlayerName` which accepts a Velocity `Pl
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>

View File

@ -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&mdash;Current_ | ✅ |
| v1.x | _v1.5.2&mdash;Current_ | ✅ |
## Table of contents
1. Adding the API to your project

View File

@ -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

View File

@ -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() {

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("")),
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("")),

View File

@ -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];

View File

@ -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<TabPlayer> players;
private final ConcurrentHashMap<UUID, TabPlayer> players;
private final ConcurrentLinkedQueue<String> 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<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")
@ -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<String> 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);
}
});
});