Added VelocitabAPI, Vanish integration system (#106)

* Added support for VanishIntegration

* Merged

* Added API and improved vanish system

* Fixed problem with API

* Fixed import problem

* Commit with requested changes

* first test

* Added NotNull missing annotations

* Fixed all requested changes

* Fixed logic problem

* Revert "first test"

This reverts commit 1be3c47d9c.

* Hide nametag if the prefix & suffix are empty.

* Fixes for conversations.

* Added missing @NotNull

* Adjust repo order; use `elytrium` over `exceptionflug`

---------

Co-authored-by: William <will27528@gmail.com>
This commit is contained in:
AlexDev_ 2023-10-18 19:43:03 +02:00 committed by GitHub
parent 1e2aff4cf0
commit b128f36efa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 592 additions and 69 deletions

View File

@ -23,8 +23,8 @@ repositories {
maven { url = 'https://repo.william278.net/releases/' }
maven { url = 'https://jitpack.io/' }
maven { url = 'https://repo.minebench.de/' }
maven { url = 'https://maven.elytrium.net/repo/' }
maven { url = 'https://mvn.exceptionflug.de/repository/exceptionflug-public/' }
maven { url = "https://maven.elytrium.net/repo/" }
}
dependencies {

View File

@ -28,12 +28,13 @@ import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.scheduler.ScheduledTask;
import lombok.Getter;
import net.william278.annotaml.Annotaml;
import net.william278.desertwell.util.UpdateChecker;
import net.william278.desertwell.util.Version;
import net.william278.velocitab.api.VelocitabAPI;
import net.william278.velocitab.commands.VelocitabCommand;
import net.william278.velocitab.config.Formatter;
import net.william278.velocitab.config.Settings;
@ -42,10 +43,9 @@ import net.william278.velocitab.hook.LuckPermsHook;
import net.william278.velocitab.hook.MiniPlaceholdersHook;
import net.william278.velocitab.hook.PAPIProxyBridgeHook;
import net.william278.velocitab.packet.ScoreboardManager;
import net.william278.velocitab.player.Role;
import net.william278.velocitab.player.TabPlayer;
import net.william278.velocitab.sorting.SortingManager;
import net.william278.velocitab.tab.PlayerTabList;
import net.william278.velocitab.vanish.VanishManager;
import org.bstats.charts.SimplePie;
import org.bstats.velocity.Metrics;
import org.jetbrains.annotations.NotNull;
@ -75,6 +75,8 @@ public class Velocitab {
private List<Hook> hooks;
private ScoreboardManager scoreboardManager;
private SortingManager sortingManager;
@Getter
private VanishManager vanishManager;
@Inject
public Velocitab(@NotNull ProxyServer server, @NotNull Logger logger, @DataDirectory Path dataDirectory) {
@ -90,9 +92,11 @@ public class Velocitab {
prepareScoreboardManager();
prepareTabList();
prepareSortingManager();
prepareVanishManager();
registerCommands();
registerMetrics();
checkForUpdates();
prepareAPI();
logger.info("Successfully enabled Velocitab");
}
@ -101,6 +105,7 @@ public class Velocitab {
server.getScheduler().tasksByPlugin(this).forEach(ScheduledTask::cancel);
disableScoreboardManager();
getLuckPermsHook().ifPresent(LuckPermsHook::close);
VelocitabAPI.unregister();
logger.info("Successfully disabled Velocitab");
}
@ -173,6 +178,10 @@ public class Velocitab {
}
}
private void prepareVanishManager() {
this.vanishManager = new VanishManager(this);
}
private void prepareSortingManager() {
this.sortingManager = new SortingManager(this);
}
@ -197,16 +206,8 @@ public class Velocitab {
server.getEventManager().register(this, tabList);
}
@NotNull
public TabPlayer getTabPlayer(@NotNull Player player) {
return new TabPlayer(player,
getLuckPermsHook().map(hook -> hook.getPlayerRole(player)).orElse(Role.DEFAULT_ROLE)
);
}
@SuppressWarnings("unused")
public Optional<TabPlayer> getTabPlayer(String name) {
return server.getPlayer(name).map(this::getTabPlayer);
private void prepareAPI() {
VelocitabAPI.register(this);
}
private void registerCommands() {

View File

@ -0,0 +1,181 @@
/*
* 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 com.velocitypowered.api.proxy.Player;
import net.william278.velocitab.Velocitab;
import net.william278.velocitab.player.TabPlayer;
import net.william278.velocitab.tab.PlayerTabList;
import net.william278.velocitab.vanish.VanishIntegration;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
@SuppressWarnings("unused")
/**
* The Velocitab API class.
* Retrieve an instance of the API class via {@link #getInstance()}.
*/ public class VelocitabAPI {
// Instance of the plugin
private final Velocitab plugin;
private static VelocitabAPI instance;
@ApiStatus.Internal
protected VelocitabAPI(@NotNull Velocitab plugin) {
this.plugin = plugin;
}
/**
* Entrypoint to the {@link VelocitabAPI} API - returns an instance of the API
*
* @return instance of the HuskSync API
* @since 1.5.2
*/
@NotNull
public static VelocitabAPI getInstance() {
if (instance == null) {
throw new NotRegisteredException();
}
return instance;
}
/**
* <b>(Internal use only)</b> - Register the API.
*
* @param plugin the plugin instance
* @since 3.0
*/
@ApiStatus.Internal
public static void register(@NotNull Velocitab plugin) {
instance = new VelocitabAPI(plugin);
}
/**
* <b>(Internal use only)</b> - Unregister the API.
*/
@ApiStatus.Internal
public static void unregister() {
instance = null;
}
/**
* Returns an option of {@link TabPlayer} instance for the given Velocity {@link Player}.
*
* @param player the Velocity player to get the {@link TabPlayer} instance for
* @return the {@link TabPlayer} instance for the given player or an empty optional if the player is not in a group server
* @since 2.0
*/
public Optional<TabPlayer> getUser(@NotNull Player player) {
return plugin.getTabList().getTabPlayer(player);
}
/**
* Sets the custom name for a player.
* This will only be visible in the tab list and not in the nametag.
*
* @param player The player for whom to set the custom name
* @param name The custom name to set
*/
public void setCustomPlayerName(@NotNull Player player, @Nullable String name) {
getUser(player).ifPresent(tabPlayer -> {
tabPlayer.setCustomName(name);
plugin.getTabList().updatePlayerDisplayName(tabPlayer);
});
}
/**
* Returns the custom name of the TabPlayer, if it has been set.
*
* @param player The player for whom to get the custom name
* @return An Optional object containing the custom name, or empty if no custom name has been set.
*/
public Optional<String> getCustomPlayerName(@NotNull Player player) {
return getUser(player).flatMap(TabPlayer::getCustomName);
}
/**
* {@link PlayerTabList} handles the tab list for all players on the server groups.
*
* @return the {@link PlayerTabList} global instance.
*/
@NotNull
public PlayerTabList getTabList() {
return plugin.getTabList();
}
/**
* Sets the VanishIntegration for the VelocitabAPI.
*
* @param vanishIntegration the VanishIntegration to set
*/
public void setVanishIntegration(@NotNull VanishIntegration vanishIntegration) {
plugin.getVanishManager().setIntegration(vanishIntegration);
}
/**
* Retrieves the VanishIntegration associated with the VelocitabAPI instance.
* This integration allows checking if a player can see another player and if a player is vanished.
*
* @return The VanishIntegration instance associated with the VelocitabAPI
*/
@NotNull
public VanishIntegration getVanishIntegration() {
return plugin.getVanishManager().getIntegration();
}
/**
* Vanishes the player by hiding them from the tab list and scoreboard if enabled.
*
* @param player The player to vanish
*/
public void vanishPlayer(@NotNull Player player) {
plugin.getVanishManager().vanishPlayer(player);
}
/**
* Un-vanishes the given player by showing them in the tab list and scoreboard if enabled.
*
* @param player The player to unvanish
*/
public void unVanishPlayer(@NotNull Player player) {
plugin.getVanishManager().unVanishPlayer(player);
}
static final class NotRegisteredException extends IllegalStateException {
private static final String MESSAGE = """
Could not access the Velocitab API as it has not yet been registered. This could be because:
1) Velocitab has failed to enable successfully
2) You are attempting to access Velocitab on plugin construction/before your plugin has enabled.
3) You have shaded Velocitab into your plugin jar and need to fix your maven/gradle/build script
to only include Velocitab as a dependency and not as a shaded dependency.""";
NotRegisteredException() {
super(MESSAGE);
}
}
}

View File

@ -20,15 +20,21 @@
package net.william278.velocitab.commands;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.Player;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor;
import net.william278.desertwell.about.AboutMenu;
import net.william278.velocitab.Velocitab;
import net.william278.velocitab.player.TabPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
public final class VelocitabCommand {
private static final TextColor MAIN_COLOR = TextColor.color(0x00FB9A);
private final AboutMenu aboutMenu;
@ -69,6 +75,29 @@ public final class VelocitabCommand {
return Command.SINGLE_SUCCESS;
})
)
.then(LiteralArgumentBuilder.<CommandSource>literal("name")
.then(RequiredArgumentBuilder.<CommandSource, String>argument("name", StringArgumentType.word())
.executes(ctx -> {
if (!(ctx.getSource() instanceof Player player)) {
ctx.getSource().sendMessage(Component.text("You must be a player to use this command!", MAIN_COLOR));
return Command.SINGLE_SUCCESS;
}
String name = StringArgumentType.getString(ctx, "name");
Optional<TabPlayer> tabPlayer = plugin.getTabList().getTabPlayer(player);
if (tabPlayer.isEmpty()) {
ctx.getSource().sendMessage(Component.text("You must in a correct server!", MAIN_COLOR));
return Command.SINGLE_SUCCESS;
}
tabPlayer.get().setCustomName(name);
plugin.getTabList().updatePlayerDisplayName(tabPlayer.get());
return Command.SINGLE_SUCCESS;
})
)
)
.then(LiteralArgumentBuilder.<CommandSource>literal("reload")
.requires(src -> src.hasPermission("velocitab.command.reload"))
.executes(ctx -> {
@ -91,7 +120,7 @@ public final class VelocitabCommand {
}
ctx.getSource().sendMessage(Component
.text("An update for velocitab is available. " +
"Please update to " + checked.getLatestVersion(), MAIN_COLOR));
"Please update to " + checked.getLatestVersion(), MAIN_COLOR));
});
return Command.SINGLE_SUCCESS;
})

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.getPlayer().getUsername())),
USERNAME((plugin, player) -> plugin.getFormatter().escape(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("")),
@ -83,10 +83,4 @@ public enum Placeholder {
return replaced;
});
}
@NotNull
public static String formatSortableInt(int value, int maxValue) {
return String.format("%0" + Integer.toString(maxValue).length() + "d", maxValue - value);
}
}

View File

@ -34,12 +34,14 @@ import net.william278.velocitab.tab.PlayerTabList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class LuckPermsHook extends Hook {
private int highestWeight = Role.DEFAULT_WEIGHT;
private final LuckPerms api;
private final EventSubscription<UserDataRecalculateEvent> event;
private final Map<UUID, Long> lastUpdate;
@ -87,13 +89,9 @@ public class LuckPermsHook extends Hook {
plugin.getServer().getPlayer(event.getUser().getUniqueId())
.ifPresent(player -> plugin.getServer().getScheduler()
.buildTask(plugin, () -> {
final TabPlayer updatedPlayer = new TabPlayer(
player,
getRoleFromMetadata(event.getData().getMetaData())
);
tabList.replacePlayer(updatedPlayer);
tabList.updatePlayer(updatedPlayer);
tabList.updatePlayerDisplayName(updatedPlayer);
final TabPlayer tabPlayer = tabList.getTabPlayer(player).orElseThrow();
tabPlayer.setRole(getRoleFromMetadata(event.getData().getMetaData()));
tabList.updatePlayerDisplayName(tabPlayer);
})
.delay(500, TimeUnit.MILLISECONDS)
.schedule());

View File

@ -74,6 +74,63 @@ public class ScoreboardManager {
}
}
public void vanishPlayer(@NotNull Player player) {
if (!plugin.getSettings().isSortPlayers()) {
return;
}
final Optional<ServerConnection> optionalServerConnection = player.getCurrentServer();
if (optionalServerConnection.isEmpty()) {
return;
}
final RegisteredServer serverInfo = optionalServerConnection.get().getServer();
final List<RegisteredServer> siblings = plugin.getTabList().getGroupServers(serverInfo.getServerInfo().getName());
UpdateTeamsPacket packet = UpdateTeamsPacket.removeTeam(plugin, createdTeams.get(player.getUniqueId()));
siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> {
boolean canSee = !plugin.getVanishManager().isVanished(connected.getUsername())
|| plugin.getVanishManager().canSee(player.getUsername(), player.getUsername());
if (!canSee) {
return;
}
dispatchPacket(packet, connected);
}));
}
public void unVanishPlayer(@NotNull Player player) {
if (!plugin.getSettings().isSortPlayers()) {
return;
}
final Optional<ServerConnection> optionalServerConnection = player.getCurrentServer();
if (optionalServerConnection.isEmpty()) {
return;
}
final RegisteredServer serverInfo = optionalServerConnection.get().getServer();
final List<RegisteredServer> siblings = plugin.getTabList().getGroupServers(serverInfo.getServerInfo().getName());
final String role = createdTeams.getOrDefault(player.getUniqueId(), "");
if (role.isEmpty()) {
return;
}
final String nametag = nametags.getOrDefault(role, "");
if (nametag.isEmpty()) {
return;
}
final String[] split = nametag.split(NAMETAG_DELIMITER, 2);
final String prefix = split[0];
final String suffix = split.length > 1 ? split[1] : "";
final UpdateTeamsPacket packet = UpdateTeamsPacket.create(plugin, createdTeams.get(player.getUniqueId()), "", prefix, suffix, player.getUsername());
siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> dispatchPacket(packet, connected)));
}
public void updateRole(@NotNull Player player, @NotNull String role) {
if (!player.isActive()) {
plugin.getTabList().removeOfflinePlayer(player);
@ -81,11 +138,11 @@ public class ScoreboardManager {
}
final String name = player.getUsername();
final TabPlayer tabPlayer = plugin.getTabPlayer(player);
final TabPlayer tabPlayer = plugin.getTabList().getTabPlayer(player).orElseThrow();
tabPlayer.getNametag(plugin).thenAccept(nametag -> {
String[] split = nametag.split(player.getUsername(), 2);
String prefix = split[0];
String suffix = split.length > 1 ? split[1] : "";
final String[] split = nametag.split(player.getUsername(), 2);
final String prefix = split[0];
final String suffix = split.length > 1 ? split[1] : "";
if (!createdTeams.getOrDefault(player.getUniqueId(), "").equals(role)) {
if (createdTeams.containsKey(player.getUniqueId())) {
@ -106,8 +163,8 @@ public class ScoreboardManager {
}
public void resendAllNameTags(Player player) {
if (!plugin.getSettings().doNametags()) {
public void resendAllTeams(@NotNull Player player) {
if (!plugin.getSettings().isSendScoreboardPackets()) {
return;
}
@ -122,16 +179,31 @@ public class ScoreboardManager {
.map(RegisteredServer::getPlayersConnected)
.flatMap(Collection::stream)
.toList();
final List<String> roles = new ArrayList<>();
players.forEach(p -> {
if (p == player || !p.isActive()) {
return;
}
if (plugin.getVanishManager().isVanished(p.getUsername()) ||
!plugin.getVanishManager().canSee(player.getUsername(), p.getUsername())) {
return;
}
final String role = createdTeams.getOrDefault(p.getUniqueId(), "");
if (role.isEmpty()) {
return;
}
// Prevent duplicate packets
if (roles.contains(role)) {
return;
}
roles.add(role);
final String nametag = nametags.getOrDefault(role, "");
if (nametag.isEmpty()) {
return;
@ -168,6 +240,12 @@ public class ScoreboardManager {
final List<RegisteredServer> siblings = plugin.getTabList().getGroupServers(serverInfo.getServerInfo().getName());
siblings.forEach(server -> server.getPlayersConnected().forEach(connected -> {
try {
final boolean canSee = !plugin.getVanishManager().isVanished(connected.getUsername())
|| plugin.getVanishManager().canSee(player.getUsername(), player.getUsername());
if (!canSee) {
return;
}
final ConnectedPlayer connectedPlayer = (ConnectedPlayer) connected;
connectedPlayer.getConnection().write(packet);
} catch (Throwable e) {

View File

@ -69,7 +69,7 @@ public class UpdateTeamsPacket implements MinecraftPacket {
.mode(UpdateMode.CREATE_TEAM)
.displayName(displayName)
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
.nameTagVisibility(NameTagVisibility.ALWAYS)
.nameTagVisibility(isNametagPresent(prefix, suffix) ? NameTagVisibility.ALWAYS : NameTagVisibility.NEVER)
.collisionRule(CollisionRule.ALWAYS)
.color(getLastColor(prefix))
.prefix(prefix == null ? "" : prefix)
@ -77,6 +77,10 @@ public class UpdateTeamsPacket implements MinecraftPacket {
.entities(Arrays.asList(teamMembers));
}
private static boolean isNametagPresent(@Nullable String prefix, @Nullable String suffix) {
return prefix != null && !prefix.isEmpty() || suffix != null && !suffix.isEmpty();
}
@NotNull
protected static UpdateTeamsPacket changeNameTag(@NotNull Velocitab plugin, @NotNull String teamName,
@Nullable String prefix, @Nullable String suffix) {
@ -85,7 +89,7 @@ public class UpdateTeamsPacket implements MinecraftPacket {
.mode(UpdateMode.UPDATE_INFO)
.displayName(teamName)
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
.nameTagVisibility(NameTagVisibility.ALWAYS)
.nameTagVisibility(isNametagPresent(prefix, suffix) ? NameTagVisibility.ALWAYS : NameTagVisibility.NEVER)
.collisionRule(CollisionRule.ALWAYS)
.color(getLastColor(prefix))
.prefix(prefix == null ? "" : prefix)

View File

@ -21,18 +21,21 @@ package net.william278.velocitab.player;
import com.velocitypowered.api.proxy.Player;
import lombok.Getter;
import lombok.Setter;
import net.kyori.adventure.text.Component;
import net.william278.velocitab.Velocitab;
import net.william278.velocitab.config.Placeholder;
import net.william278.velocitab.tab.PlayerTabList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public final class TabPlayer implements Comparable<TabPlayer> {
private final Player player;
private final Role role;
@Setter
private Role role;
@Getter
private int headerIndex = 0;
@Getter
@ -40,6 +43,8 @@ public final class TabPlayer implements Comparable<TabPlayer> {
@Getter
private Component lastDisplayname;
private String teamName;
@Nullable
private String customName;
public TabPlayer(@NotNull Player player, @NotNull Role role) {
this.player = player;
@ -153,6 +158,24 @@ public final class TabPlayer implements Comparable<TabPlayer> {
}
}
/**
* Returns the custom name of the TabPlayer, if it has been set.
*
* @return An Optional object containing the custom name, or empty if no custom name has been set.
*/
public Optional<String> getCustomName() {
return Optional.ofNullable(customName);
}
/**
* Sets the custom name of the TabPlayer.
*
* @param customName The custom name to set
*/
public void setCustomName(@Nullable String customName) {
this.customName = customName;
}
@Override
public int compareTo(@NotNull TabPlayer o) {
final int roleDifference = role.compareTo(o.role);

View File

@ -33,6 +33,7 @@ import com.velocitypowered.api.scheduler.ScheduledTask;
import net.kyori.adventure.text.Component;
import net.william278.velocitab.Velocitab;
import net.william278.velocitab.config.Placeholder;
import net.william278.velocitab.player.Role;
import net.william278.velocitab.player.TabPlayer;
import org.jetbrains.annotations.NotNull;
@ -63,17 +64,17 @@ public class PlayerTabList {
}
}
public Optional<TabPlayer> getTabPlayer(@NotNull Player player) {
return players.stream().filter(tabPlayer -> tabPlayer.getPlayer().getUniqueId().equals(player.getUniqueId())).findFirst();
}
@SuppressWarnings("UnstableApiUsage")
@Subscribe
public void onPlayerJoin(@NotNull ServerPostConnectEvent event) {
final Player joined = event.getPlayer();
plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(joined));
// Remove the player from the tracking list if they are switching servers
final RegisteredServer previousServer = event.getPreviousServer();
if (previousServer != null) {
players.removeIf(player -> player.getPlayer().getUniqueId().equals(joined.getUniqueId()));
}
// Get the servers in the group from the joined server name
// If the server is not in a group, use fallback
@ -90,10 +91,11 @@ public class PlayerTabList {
return;
}
// Add the player to the tracking list
final TabPlayer tabPlayer = plugin.getTabPlayer(joined);
// Add the player to the tracking list if they are not already listed
final TabPlayer tabPlayer = getTabPlayer(joined).orElseGet(() -> createTabPlayer(joined));
players.add(tabPlayer);
final boolean isVanished = plugin.getVanishManager().isVanished(joined.getUsername());
// Update lists
plugin.getServer().getScheduler()
.buildTask(plugin, () -> {
@ -104,22 +106,31 @@ public class PlayerTabList {
&& !serversInGroup.get().contains(player.getServerName())) {
continue;
}
// Create or update TAB list entries for all players
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)
);
addPlayerToTabList(player, tabPlayer);
// check if current player can see the joined player
if (!isVanished || plugin.getVanishManager().canSee(player.getPlayer().getUsername(), joined.getUsername())) {
addPlayerToTabList(player, tabPlayer);
} else {
player.getPlayer().getTabList().removeEntry(joined.getUniqueId());
}
// check if joined player can see current player
if ((plugin.getVanishManager().isVanished(player.getPlayer().getUsername()) ||
!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)
);
}
player.sendHeaderAndFooter(this);
}
plugin.getScoreboardManager().ifPresent(s -> {
s.resendAllNameTags(joined);
plugin.getTabPlayer(joined).getTeamName(plugin).thenAccept(t -> s.updateRole(joined, t));
s.resendAllTeams(joined);
tabPlayer.getTeamName(plugin).thenAccept(t -> s.updateRole(joined, t));
});
})
.delay(500, TimeUnit.MILLISECONDS)
@ -136,6 +147,16 @@ public class PlayerTabList {
.build());
}
@NotNull
private TabListEntry createEntry(@NotNull TabPlayer player, @NotNull TabList tabList, Component displayName) {
return TabListEntry.builder()
.profile(player.getPlayer().getGameProfile())
.displayName(displayName)
.latency(0)
.tabList(tabList)
.build();
}
private void addPlayerToTabList(@NotNull TabPlayer player, @NotNull TabPlayer newPlayer) {
if (newPlayer.getPlayer().getUniqueId().equals(player.getPlayer().getUniqueId())) {
return;
@ -152,6 +173,20 @@ 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) {
@ -181,10 +216,11 @@ public class PlayerTabList {
}
// Replace a player in the tab list
public void replacePlayer(@NotNull TabPlayer tabPlayer) {
players.removeIf(player -> player.getPlayer().getUniqueId().equals(tabPlayer.getPlayer().getUniqueId()));
players.add(tabPlayer);
@NotNull
public TabPlayer createTabPlayer(@NotNull Player player) {
return new TabPlayer(player,
plugin.getLuckPermsHook().map(hook -> hook.getPlayerRole(player)).orElse(Role.DEFAULT_ROLE)
);
}
// Update a player's name in the tab list
@ -204,19 +240,25 @@ public class PlayerTabList {
});
}
public void updatePlayerDisplayName(TabPlayer tabPlayer) {
public void updatePlayerDisplayName(@NotNull TabPlayer tabPlayer) {
final Component lastDisplayName = tabPlayer.getLastDisplayname();
tabPlayer.getDisplayName(plugin).thenAccept(displayName -> {
if (displayName == null || displayName.equals(lastDisplayName)) {
return;
}
players.forEach(player ->
player.getPlayer().getTabList().getEntries().stream()
.filter(e -> e.getProfile().getId().equals(tabPlayer.getPlayer().getUniqueId())).findFirst()
.ifPresent(entry -> entry.setDisplayName(displayName)));
});
boolean isVanished = plugin.getVanishManager().isVanished(tabPlayer.getPlayer().getUsername());
players.forEach(player -> {
if (isVanished && !plugin.getVanishManager().canSee(player.getPlayer().getUsername(), tabPlayer.getPlayer().getUsername())) {
return;
}
player.getPlayer().getTabList().getEntries().stream()
.filter(e -> e.getProfile().getId().equals(tabPlayer.getPlayer().getUniqueId())).findFirst()
.ifPresent(entry -> entry.setDisplayName(displayName));
});
});
}
// Update the display names of all listed players
@ -293,7 +335,7 @@ public class PlayerTabList {
* @return The servers in the same group as the given server, empty if the server is not in a group and fallback is disabled
*/
@NotNull
public Optional<List<String>> getGroupNames(String serverName) {
public Optional<List<String>> getGroupNames(@NotNull String serverName) {
return plugin.getSettings().getServerGroups().values().stream()
.filter(servers -> servers.contains(serverName))
.findFirst()
@ -319,7 +361,7 @@ public class PlayerTabList {
* @return The servers in the same group as the given server, empty if the server is not in a group and fallback is disabled
*/
@NotNull
public List<RegisteredServer> getGroupServers(String serverName) {
public List<RegisteredServer> getGroupServers(@NotNull String serverName) {
return plugin.getServer().getAllServers().stream()
.filter(server -> plugin.getSettings().getServerGroups().values().stream()
.filter(servers -> servers.contains(serverName))
@ -342,4 +384,33 @@ public class PlayerTabList {
public void removeOfflinePlayer(@NotNull Player player) {
players.removeIf(tabPlayer -> tabPlayer.getPlayer().getUniqueId().equals(player.getUniqueId()));
}
public void vanishPlayer(@NotNull TabPlayer tabPlayer) {
players.forEach(p -> {
if (p.getPlayer().equals(tabPlayer.getPlayer())) {
return;
}
if (!plugin.getVanishManager().canSee(p.getPlayer().getUsername(), tabPlayer.getPlayer().getUsername())) {
p.getPlayer().getTabList().removeEntry(tabPlayer.getPlayer().getUniqueId());
}
});
}
public void unVanishPlayer(@NotNull TabPlayer tabPlayer) {
final UUID uuid = tabPlayer.getPlayer().getUniqueId();
tabPlayer.getDisplayName(plugin).thenAccept(c -> {
players.forEach(p -> {
if (p.getPlayer().equals(tabPlayer.getPlayer())) {
return;
}
if (!p.getPlayer().getTabList().containsEntry(uuid)) {
createEntry(tabPlayer, p.getPlayer().getTabList(), c);
}
});
});
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.vanish;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.NotNull;
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public final class DefaultVanishIntegration implements VanishIntegration {
@Override
public boolean canSee(@NotNull String name, @NotNull String otherName) {
return true;
}
@Override
public boolean isVanished(@NotNull String name) {
return false;
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.vanish;
import org.jetbrains.annotations.NotNull;
public interface VanishIntegration {
boolean canSee(@NotNull String name, @NotNull String otherName);
boolean isVanished(@NotNull String name);
}

View File

@ -0,0 +1,75 @@
/*
* 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.vanish;
import com.velocitypowered.api.proxy.Player;
import net.william278.velocitab.Velocitab;
import net.william278.velocitab.player.TabPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
public class VanishManager {
private final Velocitab plugin;
private VanishIntegration integration;
public VanishManager(@NotNull Velocitab plugin) {
this.plugin = plugin;
setIntegration(new DefaultVanishIntegration());
}
public void setIntegration(@NotNull VanishIntegration integration) {
this.integration = integration;
}
@NotNull
public VanishIntegration getIntegration() {
return integration;
}
public boolean canSee(@NotNull String name, @NotNull String otherName) {
return integration.canSee(name, otherName);
}
public boolean isVanished(@NotNull String name) {
return integration.isVanished(name);
}
public void vanishPlayer(@NotNull Player player) {
final Optional<TabPlayer> tabPlayer = plugin.getTabList().getTabPlayer(player);
if (tabPlayer.isEmpty()) {
return;
}
plugin.getTabList().vanishPlayer(tabPlayer.get());
plugin.getScoreboardManager().ifPresent(scoreboardManager -> scoreboardManager.vanishPlayer(player));
}
public void unVanishPlayer(@NotNull Player player) {
final Optional<TabPlayer> tabPlayer = plugin.getTabList().getTabPlayer(player);
if (tabPlayer.isEmpty()) {
return;
}
plugin.getTabList().unVanishPlayer(tabPlayer.get());
plugin.getScoreboardManager().ifPresent(scoreboardManager -> scoreboardManager.unVanishPlayer(player));
}
}