Added PlayerAddedToTabEvent, improved PlayerTabList performance and more ()

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);
}
});
});