Added support for nametags and fixed a few problems (#84)

* Added regex check for placeholders to avoid useless requests.
Added support for custom nametags. Due to minecraft limit only legacy chatcolor are supported.
Team names now are unique, so 1 team can have max 1 player.
Fixed problem with luckperms event bus while reloading the plugin.

* Update src/main/java/net/william278/velocitab/config/Placeholder.java

Co-authored-by: William <will27528@gmail.com>

* Update src/main/java/net/william278/velocitab/hook/LuckPermsHook.java

Co-authored-by: William <will27528@gmail.com>

* Update src/main/java/net/william278/velocitab/config/Formatter.java

Co-authored-by: William <will27528@gmail.com>

* Update src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java

Co-authored-by: William <will27528@gmail.com>

* Fixed problem while updating display names. Changed a few method signature as requested in pr. Applied changes of pr.

* Fixed problems after merging with upstream, fixed problem with player team color on join.

* Update src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java

Co-authored-by: William <will27528@gmail.com>

* Update src/main/java/net/william278/velocitab/packet/ScoreboardManager.java

Co-authored-by: William <will27528@gmail.com>

* Update src/main/java/net/william278/velocitab/packet/ScoreboardManager.java

Co-authored-by: William <will27528@gmail.com>

* Update src/main/java/net/william278/velocitab/packet/ScoreboardManager.java

Co-authored-by: William <will27528@gmail.com>

* Update src/main/java/net/william278/velocitab/config/Formatter.java

Co-authored-by: William <will27528@gmail.com>

* Update src/main/java/net/william278/velocitab/player/TabPlayer.java

Co-authored-by: William <will27528@gmail.com>

* Fix username replacement in scoreboard and code typo

This commit resolves two issues. Firstly, changed the variable that we split the nametag on in `ScoreboardManager` from a hardcoded string to the player's specific username. This rectifies an issue where incorrect splitting occurred if the username wasn't exactly "%username%". Secondly, fixed a miswritten method call in `Formatter` from '..legacySection()' to '.legacySection()', correcting a syntax error. Lastly, removed superfluous replacement in `TabPlayer's` getNametag method as it was already handled in `ScoreboardManager`.

---------

Co-authored-by: William <will27528@gmail.com>
This commit is contained in:
AlexDev_ 2023-09-22 18:57:51 +02:00 committed by GitHub
parent 8ae25521dd
commit 75d9f32010
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 284 additions and 81 deletions

View File

@ -95,6 +95,7 @@ public class Velocitab {
@Subscribe @Subscribe
public void onProxyShutdown(@NotNull ProxyShutdownEvent event) { public void onProxyShutdown(@NotNull ProxyShutdownEvent event) {
disableScoreboardManager(); disableScoreboardManager();
getLuckPermsHook().ifPresent(LuckPermsHook::close);
logger.info("Successfully disabled Velocitab"); logger.info("Successfully disabled Velocitab");
} }
@ -119,6 +120,12 @@ public class Velocitab {
new File(dataDirectory.toFile(), "config.yml"), new File(dataDirectory.toFile(), "config.yml"),
new Settings(this) new Settings(this)
).get(); ).get();
settings.getNametags().values().stream()
.filter(nametag -> !nametag.contains("%username%")).forEach(nametag -> {
logger.warn("Nametag '" + nametag + "' does not contain %username% - removing");
settings.getNametags().remove(nametag);
});
} catch (IOException | InvocationTargetException | InstantiationException | IllegalAccessException e) { } catch (IOException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
logger.error("Failed to load config file: " + e.getMessage(), e); logger.error("Failed to load config file: " + e.getMessage(), e);
} }
@ -156,7 +163,7 @@ public class Velocitab {
} }
private void disableScoreboardManager() { private void disableScoreboardManager() {
if (scoreboardManager !=null && settings.isSortPlayers()) { if (scoreboardManager != null && settings.isSortPlayers()) {
scoreboardManager.unregisterPacket(); scoreboardManager.unregisterPacket();
} }
} }
@ -184,6 +191,10 @@ public class Velocitab {
); );
} }
public Optional<TabPlayer> getTabPlayer(String name) {
return server.getPlayer(name).map(this::getTabPlayer);
}
private void registerCommands() { private void registerCommands() {
final BrigadierCommand command = new VelocitabCommand(this).command(); final BrigadierCommand command = new VelocitabCommand(this).command();
server.getCommandManager().register( server.getCommandManager().register(

View File

@ -79,6 +79,11 @@ public enum Formatter {
return formatter.apply(text, player, plugin); return formatter.apply(text, player, plugin);
} }
@NotNull
public String formatLegacySymbols(@NotNull String text, @NotNull TabPlayer player, @NotNull Velocitab plugin) {
return LegacyComponentSerializer.legacySection()
.serialize(format(text, player, plugin));
}
@NotNull @NotNull
public String escape(@NotNull String text) { public String escape(@NotNull String text) {
return escaper.apply(text); return escaper.apply(text);

View File

@ -65,6 +65,10 @@ public enum Placeholder {
} }
final String replaced = format; final String replaced = format;
if (!replaced.matches("%.*?%")) {
return CompletableFuture.completedFuture(replaced);
}
return plugin.getPAPIProxyBridgeHook() return plugin.getPAPIProxyBridgeHook()
.map(hook -> hook.formatPlaceholders(replaced, player.getPlayer())) .map(hook -> hook.formatPlaceholders(replaced, player.getPlayer()))
.orElse(CompletableFuture.completedFuture(replaced)); .orElse(CompletableFuture.completedFuture(replaced));

View File

@ -60,6 +60,11 @@ public class Settings {
@YamlKey("formats") @YamlKey("formats")
private Map<String, String> formats = Map.of("default", "&7[%server%] &f%prefix%%username%"); private Map<String, String> formats = Map.of("default", "&7[%server%] &f%prefix%%username%");
@Getter
@YamlKey("nametags")
@YamlComment("Nametag(s) to display above players' heads for each server group. To disable, set to empty")
private Map<String, String> nametags = Map.of("default", "&f%prefix%%username%&f%suffix%");
@Getter @Getter
@YamlComment("Which text formatter to use (MINEDOWN, MINIMESSAGE, or LEGACY)") @YamlComment("Which text formatter to use (MINEDOWN, MINIMESSAGE, or LEGACY)")
@YamlKey("formatting_type") @YamlKey("formatting_type")
@ -165,6 +170,16 @@ public class Settings {
formats.getOrDefault(serverGroup, "%username%")); formats.getOrDefault(serverGroup, "%username%"));
} }
@NotNull
public String getNametag(@NotNull String serverGroup) {
return StringEscapeUtils.unescapeJava(
nametags.getOrDefault(serverGroup, ""));
}
public boolean areNametagsEnabled() {
return !nametags.isEmpty();
}
/** /**
* Get display name for the server * Get display name for the server
* *

View File

@ -23,6 +23,7 @@ import com.velocitypowered.api.proxy.Player;
import net.luckperms.api.LuckPerms; import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider; import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.cacheddata.CachedMetaData; import net.luckperms.api.cacheddata.CachedMetaData;
import net.luckperms.api.event.EventSubscription;
import net.luckperms.api.event.user.UserDataRecalculateEvent; import net.luckperms.api.event.user.UserDataRecalculateEvent;
import net.luckperms.api.model.group.Group; import net.luckperms.api.model.group.Group;
import net.luckperms.api.model.user.User; import net.luckperms.api.model.user.User;
@ -42,11 +43,16 @@ public class LuckPermsHook extends Hook {
private int highestWeight = Role.DEFAULT_WEIGHT; private int highestWeight = Role.DEFAULT_WEIGHT;
private final LuckPerms api; private final LuckPerms api;
private final EventSubscription<UserDataRecalculateEvent> event;
public LuckPermsHook(@NotNull Velocitab plugin) throws IllegalStateException { public LuckPermsHook(@NotNull Velocitab plugin) throws IllegalStateException {
super(plugin); super(plugin);
this.api = LuckPermsProvider.get(); this.api = LuckPermsProvider.get();
api.getEventBus().subscribe(plugin, UserDataRecalculateEvent.class, this::onLuckPermsGroupUpdate); event = api.getEventBus().subscribe(plugin, UserDataRecalculateEvent.class, this::onLuckPermsGroupUpdate);
}
public void close() {
event.close();
} }
@NotNull @NotNull
@ -81,6 +87,7 @@ public class LuckPermsHook extends Hook {
); );
tabList.replacePlayer(updatedPlayer); tabList.replacePlayer(updatedPlayer);
tabList.updatePlayer(updatedPlayer); tabList.updatePlayer(updatedPlayer);
tabList.updatePlayerDisplayName(updatedPlayer);
}) })
.delay(500, TimeUnit.MILLISECONDS) .delay(500, TimeUnit.MILLISECONDS)
.schedule()); .schedule());

View File

@ -32,7 +32,7 @@ public class PAPIProxyBridgeHook extends Hook {
public PAPIProxyBridgeHook(@NotNull Velocitab plugin) { public PAPIProxyBridgeHook(@NotNull Velocitab plugin) {
super(plugin); super(plugin);
this.api = PlaceholderAPI.getInstance(); this.api = PlaceholderAPI.createInstance();
this.api.setCacheExpiry(Math.max(0, plugin.getSettings().getPapiCacheTime())); this.api.setCacheExpiry(Math.max(0, plugin.getSettings().getPapiCacheTime()));
} }

View File

@ -21,15 +21,18 @@ package net.william278.velocitab.packet;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import net.william278.velocitab.Velocitab; import net.william278.velocitab.Velocitab;
import net.william278.velocitab.player.TabPlayer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.slf4j.event.Level; import org.slf4j.event.Level;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.concurrent.ConcurrentHashMap;
import static com.velocitypowered.api.network.ProtocolVersion.*; import static com.velocitypowered.api.network.ProtocolVersion.*;
@ -37,14 +40,14 @@ public class ScoreboardManager {
private PacketRegistration<UpdateTeamsPacket> packetRegistration; private PacketRegistration<UpdateTeamsPacket> packetRegistration;
private final Velocitab plugin; private final Velocitab plugin;
private final Map<UUID, List<String>> createdTeams;
private final Map<UUID, Map<String, String>> roleMappings;
private final Set<TeamsPacketAdapter> versions; private final Set<TeamsPacketAdapter> versions;
private final Map<UUID, String> createdTeams;
private final Map<String, String> nametags;
public ScoreboardManager(@NotNull Velocitab velocitab) { public ScoreboardManager(@NotNull Velocitab velocitab) {
this.plugin = velocitab; this.plugin = velocitab;
this.createdTeams = new HashMap<>(); this.createdTeams = new ConcurrentHashMap<>();
this.roleMappings = new HashMap<>(); this.nametags = new ConcurrentHashMap<>();
this.versions = new HashSet<>(); this.versions = new HashSet<>();
this.registerVersions(); this.registerVersions();
} }
@ -64,44 +67,78 @@ public class ScoreboardManager {
} }
public void resetCache(@NotNull Player player) { public void resetCache(@NotNull Player player) {
createdTeams.remove(player.getUniqueId()); String team = createdTeams.remove(player.getUniqueId());
roleMappings.remove(player.getUniqueId()); if (team != null) {
dispatchGroupPacket(UpdateTeamsPacket.removeTeam(plugin, team), player);
}
} }
public void setRoles(@NotNull Player player, @NotNull Map<String, String> playerRoles) { public void updateRole(@NotNull Player player, @NotNull String role) {
if (!player.isActive()) { if (!player.isActive()) {
plugin.getTabList().removeOfflinePlayer(player); plugin.getTabList().removeOfflinePlayer(player);
return; return;
} }
playerRoles.entrySet().stream()
.collect(Collectors.groupingBy( final String name = player.getUsername();
Map.Entry::getValue, final TabPlayer tabPlayer = plugin.getTabPlayer(player);
Collectors.mapping(Map.Entry::getKey, Collectors.toList()) tabPlayer.getNametag(plugin).thenAccept(nametag -> {
)) String[] split = nametag.split(player.getUsername(), 2);
.forEach((role, players) -> updateRoles(player, role, players.toArray(new String[0]))); String prefix = split[0];
String suffix = split.length > 1 ? split[1] : "";
if (!createdTeams.getOrDefault(player.getUniqueId(), "").equals(role)) {
createdTeams.computeIfAbsent(player.getUniqueId(), k -> role);
this.nametags.put(role, prefix + ":::" + suffix);
dispatchGroupPacket(UpdateTeamsPacket.create(plugin, role, "", prefix, suffix, name), player);
} else if (!this.nametags.getOrDefault(role, "").equals(prefix + ":::" + suffix)) {
this.nametags.put(role, prefix + ":::" + suffix);
dispatchGroupPacket(UpdateTeamsPacket.changeNameTag(plugin, role, prefix, suffix), player);
}
}).exceptionally(e -> {
plugin.log(Level.ERROR, "Failed to update role for " + player.getUsername(), e);
return null;
});
} }
public void updateRoles(@NotNull Player player, @NotNull String role, @NotNull String... playerNames) {
if (!player.isActive()) { public void resendAllNameTags(Player player) {
plugin.getTabList().removeOfflinePlayer(player);
if(!plugin.getSettings().areNametagsEnabled()) {
return; return;
} }
if (!createdTeams.getOrDefault(player.getUniqueId(), List.of()).contains(role)) {
dispatchPacket(UpdateTeamsPacket.create(plugin, role, playerNames), player); final Optional<ServerConnection> optionalServerConnection = player.getCurrentServer();
createdTeams.computeIfAbsent(player.getUniqueId(), k -> new ArrayList<>()).add(role); if (optionalServerConnection.isEmpty()) {
roleMappings.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>()).put(player.getUsername(), role); return;
} else {
roleMappings.getOrDefault(player.getUniqueId(), Map.of())
.entrySet().stream()
.filter((entry) -> List.of(playerNames).contains(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
.forEach((playerName, oldRole) -> dispatchPacket(
UpdateTeamsPacket.removeFromTeam(plugin, oldRole, playerName),
player
));
dispatchPacket(UpdateTeamsPacket.addToTeam(plugin, role, playerNames), player);
roleMappings.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>()).put(player.getUsername(), role);
} }
RegisteredServer serverInfo = optionalServerConnection.get().getServer();
List<RegisteredServer> siblings = plugin.getTabList().getGroupServers(serverInfo.getServerInfo().getName());
List<Player> players = siblings.stream().map(RegisteredServer::getPlayersConnected).flatMap(Collection::stream).toList();
players.forEach(p -> {
if (p == player || !p.isActive()) {
return;
}
final String role = createdTeams.getOrDefault(p.getUniqueId(), "");
if (role.isEmpty()) {
return;
}
final String nametag = nametags.getOrDefault(role, "");
if (nametag.isEmpty()) {
return;
}
String[] split = nametag.split(":::", 2);
String prefix = split[0];
String suffix = split.length > 1 ? split[1] : "";
dispatchPacket(UpdateTeamsPacket.create(plugin, role, "", prefix, suffix, p.getUsername()), player);
});
} }
private void dispatchPacket(@NotNull UpdateTeamsPacket packet, @NotNull Player player) { private void dispatchPacket(@NotNull UpdateTeamsPacket packet, @NotNull Player player) {
@ -118,6 +155,29 @@ public class ScoreboardManager {
} }
} }
private void dispatchGroupPacket(@NotNull UpdateTeamsPacket packet, @NotNull Player player) {
Optional<ServerConnection> optionalServerConnection = player.getCurrentServer();
if (optionalServerConnection.isEmpty()) {
return;
}
RegisteredServer serverInfo = optionalServerConnection.get().getServer();
List<RegisteredServer> siblings = plugin.getTabList().getGroupServers(serverInfo.getServerInfo().getName());
siblings.forEach(s -> {
s.getPlayersConnected().forEach(p -> {
try {
final ConnectedPlayer connectedPlayer = (ConnectedPlayer) p;
connectedPlayer.getConnection().write(packet);
} catch (Exception e) {
plugin.log(Level.ERROR, "Failed to dispatch packet (is the client or server modded or using an illegal version?)", e);
}
});
});
}
public void registerPacket() { public void registerPacket() {
try { try {
packetRegistration = PacketRegistration.of(UpdateTeamsPacket.class) packetRegistration = PacketRegistration.of(UpdateTeamsPacket.class)

View File

@ -30,6 +30,7 @@ import net.william278.velocitab.Velocitab;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -61,18 +62,32 @@ public class UpdateTeamsPacket implements MinecraftPacket {
} }
@NotNull @NotNull
protected static UpdateTeamsPacket create(@NotNull Velocitab plugin, @NotNull String teamName, @NotNull String... teamMembers) { protected static UpdateTeamsPacket create(@NotNull Velocitab plugin, @NotNull String teamName, @NotNull String displayName, @Nullable String prefix, @Nullable String suffix, @NotNull String... teamMembers) {
return new UpdateTeamsPacket(plugin) return new UpdateTeamsPacket(plugin)
.teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName) .teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName)
.mode(UpdateMode.CREATE_TEAM) .mode(UpdateMode.CREATE_TEAM)
.displayName(displayName)
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
.nameTagVisibility(NameTagVisibility.ALWAYS)
.collisionRule(CollisionRule.ALWAYS)
.color(getLastColor(prefix))
.prefix(prefix == null ? "" : prefix)
.suffix(suffix == null ? "" : suffix)
.entities(Arrays.asList(teamMembers));
}
@NotNull
protected static UpdateTeamsPacket changeNameTag(@NotNull Velocitab plugin, @NotNull String teamName, @Nullable String prefix, @Nullable String suffix) {
return new UpdateTeamsPacket(plugin)
.teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName)
.mode(UpdateMode.UPDATE_INFO)
.displayName(teamName) .displayName(teamName)
.friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY)) .friendlyFlags(List.of(FriendlyFlag.CAN_HURT_FRIENDLY))
.nameTagVisibility(NameTagVisibility.ALWAYS) .nameTagVisibility(NameTagVisibility.ALWAYS)
.collisionRule(CollisionRule.ALWAYS) .collisionRule(CollisionRule.ALWAYS)
.color(15) .color(getLastColor(prefix))
.prefix("") .prefix(prefix == null ? "" : prefix)
.suffix("") .suffix(suffix == null ? "" : suffix);
.entities(Arrays.asList(teamMembers));
} }
@NotNull @NotNull
@ -91,6 +106,64 @@ public class UpdateTeamsPacket implements MinecraftPacket {
.entities(Arrays.asList(teamMembers)); .entities(Arrays.asList(teamMembers));
} }
@NotNull
protected static UpdateTeamsPacket removeTeam(@NotNull Velocitab plugin, @NotNull String teamName) {
return new UpdateTeamsPacket(plugin)
.teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName)
.mode(UpdateMode.REMOVE_TEAM);
}
public static int getLastColor(@Nullable String text) {
if (text == null) {
return 15;
}
int intvar = text.lastIndexOf("§");
if (intvar == -1 || intvar == text.length() - 1) {
return 15;
}
String last = text.substring(intvar, intvar + 2);
return TeamColor.getColorId(last.charAt(1));
}
public enum TeamColor {
BLACK('0', 0),
DARK_BLUE('1', 1),
DARK_GREEN('2', 2),
DARK_AQUA('3', 3),
DARK_RED('4', 4),
DARK_PURPLE('5', 5),
GOLD('6', 6),
GRAY('7', 7),
DARK_GRAY('8', 8),
BLUE('9', 9),
GREEN('a', 10),
AQUA('b', 11),
RED('c', 12),
LIGHT_PURPLE('d', 13),
YELLOW('e', 14),
WHITE('f', 15),
OBFUSCATED('k', 16),
BOLD('l', 17),
STRIKETHROUGH('m', 18),
UNDERLINED('n', 19),
ITALIC('o', 20),
RESET('r', 21);
private final char colorChar;
private final int id;
TeamColor(char colorChar, int id) {
this.colorChar= colorChar;
this.id = id;
}
public static int getColorId(char var) {
return Arrays.stream(values()).filter(color -> color.colorChar == var).map(c -> c.id).findFirst().orElse(15);
}
}
@Override @Override
public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException("Operation not supported"); throw new UnsupportedOperationException("Operation not supported");

View File

@ -20,6 +20,7 @@
package net.william278.velocitab.player; package net.william278.velocitab.player;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import lombok.Getter;
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.config.Placeholder; import net.william278.velocitab.config.Placeholder;
@ -36,8 +37,12 @@ public final class TabPlayer implements Comparable<TabPlayer> {
private final Player player; private final Player player;
private final Role role; private final Role role;
private final int highestWeight; private final int highestWeight;
@Getter
private int headerIndex = 0; private int headerIndex = 0;
@Getter
private int footerIndex = 0; private int footerIndex = 0;
@Getter
private Component lastDisplayname;
public TabPlayer(@NotNull Player player, @NotNull Role role, int highestWeight) { public TabPlayer(@NotNull Player player, @NotNull Role role, int highestWeight) {
this.player = player; this.player = player;
@ -105,7 +110,15 @@ public final class TabPlayer implements Comparable<TabPlayer> {
public CompletableFuture<Component> getDisplayName(@NotNull Velocitab plugin) { public CompletableFuture<Component> getDisplayName(@NotNull Velocitab plugin) {
final String serverGroup = plugin.getSettings().getServerGroup(getServerName()); final String serverGroup = plugin.getSettings().getServerGroup(getServerName());
return Placeholder.replace(plugin.getSettings().getFormat(serverGroup), plugin, this) return Placeholder.replace(plugin.getSettings().getFormat(serverGroup), plugin, this)
.thenApply(formatted -> plugin.getFormatter().format(formatted, this, plugin)); .thenApply(formatted -> plugin.getFormatter().format(formatted, this, plugin))
.thenApply(c -> this.lastDisplayname = c);
}
@NotNull
public CompletableFuture<String> getNametag(@NotNull Velocitab plugin) {
final String serverGroup = plugin.getSettings().getServerGroup(getServerName());
return Placeholder.replace(plugin.getSettings().getNametag(serverGroup), plugin, this)
.thenApply(formatted -> plugin.getFormatter().formatLegacySymbols(formatted, this, plugin));
} }
@ -113,18 +126,16 @@ public final class TabPlayer implements Comparable<TabPlayer> {
public String getTeamName(@NotNull Velocitab plugin) { public String getTeamName(@NotNull Velocitab plugin) {
return plugin.getSettings().getSortingElementList().stream() return plugin.getSettings().getSortingElementList().stream()
.map(element -> element.resolve(this, plugin)) .map(element -> element.resolve(this, plugin))
.collect(Collectors.joining("-")); .collect(Collectors.joining("-"))
+ getPlayer().getUniqueId().toString().substring(0, 3);
} }
public void sendHeaderAndFooter(@NotNull PlayerTabList tabList) { public void sendHeaderAndFooter(@NotNull PlayerTabList tabList) {
tabList.getHeader(this).thenAccept(header -> tabList.getFooter(this) tabList.getHeader(this).thenAccept(header -> tabList.getFooter(this)
.thenAccept(footer -> player.sendPlayerListHeaderAndFooter(header, footer))); .thenAccept(footer -> player.sendPlayerListHeaderAndFooter(header, footer)));
} }
public int getHeaderIndex() {
return headerIndex;
}
public void incrementHeaderIndex(@NotNull Velocitab plugin) { public void incrementHeaderIndex(@NotNull Velocitab plugin) {
headerIndex++; headerIndex++;
if (headerIndex >= plugin.getSettings().getHeaderListSize(getServerGroup(plugin))) { if (headerIndex >= plugin.getSettings().getHeaderListSize(getServerGroup(plugin))) {
@ -132,10 +143,6 @@ public final class TabPlayer implements Comparable<TabPlayer> {
} }
} }
public int getFooterIndex() {
return footerIndex;
}
public void incrementFooterIndex(@NotNull Velocitab plugin) { public void incrementFooterIndex(@NotNull Velocitab plugin) {
footerIndex++; footerIndex++;
if (footerIndex >= plugin.getSettings().getFooterListSize(getServerGroup(plugin))) { if (footerIndex >= plugin.getSettings().getFooterListSize(getServerGroup(plugin))) {
@ -174,7 +181,7 @@ public final class TabPlayer implements Comparable<TabPlayer> {
? String.format("%0" + Integer.toString(orderSize).length() + "d", position) ? String.format("%0" + Integer.toString(orderSize).length() + "d", position)
: String.valueOf(orderSize); : String.valueOf(orderSize);
}), }),
SERVER_GROUP_NAME((player, plugin) -> player.getServerGroup(plugin)); SERVER_GROUP_NAME(TabPlayer::getServerGroup);
private final BiFunction<TabPlayer, Velocitab, String> elementResolver; private final BiFunction<TabPlayer, Velocitab, String> elementResolver;

View File

@ -36,9 +36,7 @@ import net.william278.velocitab.config.Placeholder;
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.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
@ -67,7 +65,6 @@ public class PlayerTabList {
final Player joined = event.getPlayer(); final Player joined = event.getPlayer();
plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(joined)); plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(joined));
// Remove the player from the tracking list if they are switching servers // Remove the player from the tracking list if they are switching servers
final RegisteredServer previousServer = event.getPreviousServer(); final RegisteredServer previousServer = event.getPreviousServer();
if (previousServer == null) { if (previousServer == null) {
@ -76,7 +73,7 @@ public class PlayerTabList {
// Get the servers in the group from the joined server name // Get the servers in the group from the joined server name
// If the server is not in a group, use fallback // If the server is not in a group, use fallback
Optional<List<String>> serversInGroup = getSiblings(joined.getCurrentServer() Optional<List<String>> serversInGroup = getGroupNames(joined.getCurrentServer()
.map(ServerConnection::getServerInfo) .map(ServerConnection::getServerInfo)
.map(ServerInfo::getName) .map(ServerInfo::getName)
.orElse("?")); .orElse("?"));
@ -91,14 +88,10 @@ public class PlayerTabList {
final TabPlayer tabPlayer = plugin.getTabPlayer(joined); final TabPlayer tabPlayer = plugin.getTabPlayer(joined);
players.add(tabPlayer); players.add(tabPlayer);
// Update lists // Update lists
plugin.getServer().getScheduler() plugin.getServer().getScheduler()
.buildTask(plugin, () -> { .buildTask(plugin, () -> {
final TabList tabList = joined.getTabList(); final TabList tabList = joined.getTabList();
final Map<String, String> playerRoles = new HashMap<>();
for (TabPlayer player : players) { for (TabPlayer player : players) {
// 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.isPresent()
@ -106,7 +99,6 @@ public class PlayerTabList {
continue; continue;
} }
playerRoles.put(player.getPlayer().getUsername(), player.getTeamName(plugin));
tabList.getEntries().stream() tabList.getEntries().stream()
.filter(e -> e.getProfile().getId().equals(player.getPlayer().getUniqueId())).findFirst() .filter(e -> e.getProfile().getId().equals(player.getPlayer().getUniqueId())).findFirst()
.ifPresentOrElse( .ifPresentOrElse(
@ -114,13 +106,13 @@ public class PlayerTabList {
() -> createEntry(player, tabList).thenAccept(tabList::addEntry) () -> createEntry(player, tabList).thenAccept(tabList::addEntry)
); );
addPlayerToTabList(player, tabPlayer); addPlayerToTabList(player, tabPlayer);
player.sendHeaderAndFooter(this); player.sendHeaderAndFooter(this);
} }
plugin.getScoreboardManager().ifPresent(s -> {
plugin.getScoreboardManager().ifPresent(manager -> manager.setRoles(joined, playerRoles)); s.resendAllNameTags(joined);
s.updateRole(joined, plugin.getTabPlayer(joined).getTeamName(plugin));
});
}) })
.delay(500, TimeUnit.MILLISECONDS) .delay(500, TimeUnit.MILLISECONDS)
.schedule(); .schedule();
@ -149,11 +141,6 @@ public class PlayerTabList {
() -> createEntry(newPlayer, player.getPlayer().getTabList()) () -> createEntry(newPlayer, player.getPlayer().getTabList())
.thenAccept(entry -> player.getPlayer().getTabList().addEntry(entry)) .thenAccept(entry -> player.getPlayer().getTabList().addEntry(entry))
); );
plugin.getScoreboardManager().ifPresent(manager -> manager.updateRoles(
player.getPlayer(),
newPlayer.getTeamName(plugin),
newPlayer.getPlayer().getUsername()
));
} }
@ -177,6 +164,9 @@ public class PlayerTabList {
})) }))
.delay(500, TimeUnit.MILLISECONDS) .delay(500, TimeUnit.MILLISECONDS)
.schedule(); .schedule();
// Delete player team
plugin.getScoreboardManager().ifPresent(manager -> manager.resetCache(event.getPlayer()));
} }
// Replace a player in the tab list // Replace a player in the tab list
@ -192,16 +182,27 @@ public class PlayerTabList {
return; return;
} }
players.forEach(player -> tabPlayer.getDisplayName(plugin).thenAccept(displayName -> { plugin.getScoreboardManager().ifPresent(manager -> manager.updateRole(
tabPlayer.getPlayer(),
tabPlayer.getTeamName(plugin)
));
}
public void updatePlayerDisplayName(TabPlayer tabPlayer) {
Component lastDisplayName = tabPlayer.getLastDisplayname();
tabPlayer.getDisplayName(plugin).thenAccept(displayName -> {
if (displayName == null || displayName.equals(lastDisplayName)) return;
players.forEach(player ->
player.getPlayer().getTabList().getEntries().stream() player.getPlayer().getTabList().getEntries().stream()
.filter(e -> e.getProfile().getId().equals(tabPlayer.getPlayer().getUniqueId())).findFirst() .filter(e -> e.getProfile().getId().equals(tabPlayer.getPlayer().getUniqueId())).findFirst()
.ifPresent(entry -> entry.setDisplayName(displayName)); .ifPresent(entry -> entry.setDisplayName(displayName)));
plugin.getScoreboardManager().ifPresent(manager -> manager.updateRoles( });
player.getPlayer(),
tabPlayer.getTeamName(plugin), }
tabPlayer.getPlayer().getUsername()
)); public void updateDisplayNames() {
})); players.forEach(this::updatePlayerDisplayName);
} }
public CompletableFuture<Component> getHeader(@NotNull TabPlayer player) { public CompletableFuture<Component> getHeader(@NotNull TabPlayer player) {
@ -231,6 +232,7 @@ public class PlayerTabList {
this.updatePlayer(player); this.updatePlayer(player);
player.sendHeaderAndFooter(this); player.sendHeaderAndFooter(this);
}); });
updateDisplayNames();
}) })
.repeat(Math.max(200, updateRate), TimeUnit.MILLISECONDS) .repeat(Math.max(200, updateRate), TimeUnit.MILLISECONDS)
.schedule(); .schedule();
@ -255,6 +257,7 @@ public class PlayerTabList {
this.updatePlayer(player); this.updatePlayer(player);
player.sendHeaderAndFooter(this); player.sendHeaderAndFooter(this);
}); });
updateDisplayNames();
} }
} }
@ -269,7 +272,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 * @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 @NotNull
public Optional<List<String>> getSiblings(String serverName) { public Optional<List<String>> getGroupNames(String serverName) {
return plugin.getSettings().getServerGroups().values().stream() return plugin.getSettings().getServerGroups().values().stream()
.filter(servers -> servers.contains(serverName)) .filter(servers -> servers.contains(serverName))
.findFirst() .findFirst()
@ -285,6 +288,24 @@ public class PlayerTabList {
}); });
} }
/**
* Get the servers in the same group as the given server, as an optional list of {@link ServerInfo}
* <p>
* If the server is not in a group, use the fallback group
* If the fallback is disabled, return an empty optional
*
* @param serverName The server name
* @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) {
return plugin.getServer().getAllServers().stream()
.filter(server -> plugin.getSettings().getServerGroups().values().stream()
.filter(servers -> servers.contains(serverName))
.anyMatch(servers -> servers.contains(server.getServerInfo().getName())))
.toList();
}
@Subscribe @Subscribe
public void proxyReload(@NotNull ProxyReloadEvent event) { public void proxyReload(@NotNull ProxyReloadEvent event) {
plugin.loadSettings(); plugin.loadSettings();