diff --git a/src/main/java/net/william278/velocitab/luckperms/LuckPermsHook.java b/src/main/java/net/william278/velocitab/luckperms/LuckPermsHook.java index a41ecca..ebbfbba 100644 --- a/src/main/java/net/william278/velocitab/luckperms/LuckPermsHook.java +++ b/src/main/java/net/william278/velocitab/luckperms/LuckPermsHook.java @@ -48,7 +48,7 @@ public class LuckPermsHook { public void onLuckPermsGroupUpdate(@NotNull UserDataRecalculateEvent event) { plugin.getServer().getPlayer(event.getUser().getUniqueId()) - .ifPresent(player -> plugin.getTabList().updatePlayer(new TabPlayer( + .ifPresent(player -> plugin.getTabList().onPlayerRoleUpdate(new TabPlayer( player, getRoleFromMetadata(event.getData().getMetaData()), getHighestWeight() @@ -80,5 +80,4 @@ public class LuckPermsHook { } - } diff --git a/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java b/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java index 0f58d25..b312cee 100644 --- a/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java +++ b/src/main/java/net/william278/velocitab/packet/ScoreboardManager.java @@ -5,21 +5,54 @@ import dev.simplix.protocolize.api.PacketDirection; import dev.simplix.protocolize.api.Protocol; import dev.simplix.protocolize.api.Protocolize; import net.william278.velocitab.Velocitab; -import net.william278.velocitab.player.TabPlayer; import org.jetbrains.annotations.NotNull; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; +import java.util.*; +import java.util.stream.Collectors; public class ScoreboardManager { private final Velocitab plugin; - - private final ConcurrentHashMap fauxTeams; + private final Map> createdTeams; + private final Map> roleMappings; public ScoreboardManager(@NotNull Velocitab velocitab) { this.plugin = velocitab; - this.fauxTeams = new ConcurrentHashMap<>(); + this.createdTeams = new HashMap<>(); + this.roleMappings = new HashMap<>(); + } + + public void resetCache(@NotNull Player player) { + createdTeams.remove(player.getUniqueId()); + roleMappings.remove(player.getUniqueId()); + } + + public void sendTeamPackets(@NotNull Player player, @NotNull Map playerRoles) { + playerRoles.entrySet().stream() + .collect(Collectors.groupingBy( + Map.Entry::getValue, + Collectors.mapping(entry -> entry.getKey().getUsername(), Collectors.toList()) + )) + .forEach((role, players) -> updateRoles(player, role, players.toArray(new String[0]))); + } + + public void updateRoles(@NotNull Player player, @NotNull String role, @NotNull String... playerNames) { + if (!createdTeams.getOrDefault(player.getUniqueId(), List.of()).contains(role)) { + dispatchPacket(UpdateTeamsPacket.create(role, playerNames), player); + createdTeams.computeIfAbsent(player.getUniqueId(), k -> new ArrayList<>()).add(role); + } 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(oldRole, playerName), player)); + dispatchPacket(UpdateTeamsPacket.addToTeam(role, playerNames), player); + roleMappings.computeIfAbsent(player.getUniqueId(), k -> new HashMap<>()).put(player.getUsername(), role); + } + } + + private void dispatchPacket(@NotNull UpdateTeamsPacket packet, @NotNull Player player) { + Protocolize.playerProvider().player(player.getUniqueId()).sendPacket(packet); } public void registerPacket() { @@ -35,40 +68,5 @@ public class ScoreboardManager { } } - public void setPlayerTeam(@NotNull TabPlayer player) { - removeTeam(player.getPlayer()); - createTeam(player.getTeamName(), player.getPlayer()); - } - - private void createTeam(@NotNull String teamName, @NotNull Player member) { - final UUID uuid = member.getUniqueId(); - try { - final UpdateTeamsPacket createTeamPacket = UpdateTeamsPacket.create(teamName, member.getUsername()); - fauxTeams.put(uuid, teamName); - plugin.getServer().getAllPlayers().stream() - .map(Player::getUniqueId) - .map(Protocolize.playerProvider()::player) - .forEach(protocolPlayer -> protocolPlayer.sendPacket(createTeamPacket)); - } catch (Exception e) { - plugin.log("Skipped setting team for " + member.getUsername()); - } - } - - public void removeTeam(@NotNull Player member) { - final UUID uuid = member.getUniqueId(); - try { - final String teamName = fauxTeams.getOrDefault(uuid, null); - if (teamName != null) { - final UpdateTeamsPacket removeTeamPacket = UpdateTeamsPacket.remove(teamName); - plugin.getServer().getAllPlayers().stream() - .map(Player::getUniqueId) - .map(Protocolize.playerProvider()::player) - .forEach(protocolPlayer -> protocolPlayer.sendPacket(removeTeamPacket)); - } - } catch (Exception e) { - plugin.log("Skipped removing team for " + member.getUsername()); - } - fauxTeams.remove(uuid); - } } diff --git a/src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java b/src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java index e27b235..37b5c31 100644 --- a/src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java +++ b/src/main/java/net/william278/velocitab/packet/UpdateTeamsPacket.java @@ -45,7 +45,7 @@ public class UpdateTeamsPacket extends AbstractPacket { private List entities; @NotNull - public static UpdateTeamsPacket create(@NotNull String teamName, @NotNull String member) { + public static UpdateTeamsPacket create(@NotNull String teamName, @NotNull String... teamMembers) { return new UpdateTeamsPacket() .teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName) .mode(UpdateMode.CREATE_TEAM) @@ -56,14 +56,23 @@ public class UpdateTeamsPacket extends AbstractPacket { .color(15) .prefix(getChatString("")) .suffix(getChatString("")) - .entities(List.of(member)); + .entities(Arrays.asList(teamMembers)); } @NotNull - public static UpdateTeamsPacket remove(@NotNull String teamName) { + public static UpdateTeamsPacket addToTeam(@NotNull String teamName, @NotNull String... teamMembers) { return new UpdateTeamsPacket() .teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName) - .mode(UpdateMode.REMOVE_TEAM); + .mode(UpdateMode.ADD_PLAYERS) + .entities(Arrays.asList(teamMembers)); + } + + @NotNull + public static UpdateTeamsPacket removeFromTeam(@NotNull String teamName, @NotNull String... teamMembers) { + return new UpdateTeamsPacket() + .teamName(teamName.length() > 16 ? teamName.substring(0, 16) : teamName) + .mode(UpdateMode.REMOVE_PLAYERS) + .entities(Arrays.asList(teamMembers)); } @Override diff --git a/src/main/java/net/william278/velocitab/player/Role.java b/src/main/java/net/william278/velocitab/player/Role.java index fcf8bf9..a723392 100644 --- a/src/main/java/net/william278/velocitab/player/Role.java +++ b/src/main/java/net/william278/velocitab/player/Role.java @@ -41,7 +41,7 @@ public class Role implements Comparable { } @NotNull - public String getStringComparableWeight(int highestWeight) { + protected String getWeightString(int highestWeight) { return String.format("%0" + (highestWeight + "").length() + "d", highestWeight - weight); } } diff --git a/src/main/java/net/william278/velocitab/player/TabPlayer.java b/src/main/java/net/william278/velocitab/player/TabPlayer.java index 473f44a..6377bc2 100644 --- a/src/main/java/net/william278/velocitab/player/TabPlayer.java +++ b/src/main/java/net/william278/velocitab/player/TabPlayer.java @@ -43,7 +43,7 @@ public final class TabPlayer implements Comparable { @NotNull public String getTeamName() { - return role.getStringComparableWeight(highestWeight) + "-" + getServerName() + "-" + player.getUsername(); + return role.getWeightString(highestWeight) + role.getName().map(name -> "-" + name).orElse(""); } public void sendHeaderAndFooter(@NotNull PlayerTabList tabList) { diff --git a/src/main/java/net/william278/velocitab/tab/PlayerTabList.java b/src/main/java/net/william278/velocitab/tab/PlayerTabList.java index 0231d95..d9d0673 100644 --- a/src/main/java/net/william278/velocitab/tab/PlayerTabList.java +++ b/src/main/java/net/william278/velocitab/tab/PlayerTabList.java @@ -1,11 +1,11 @@ package net.william278.velocitab.tab; -import com.google.common.collect.ImmutableList; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.connection.DisconnectEvent; import com.velocitypowered.api.event.player.ServerPostConnectEvent; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.player.TabList; import com.velocitypowered.api.proxy.player.TabListEntry; import com.velocitypowered.api.proxy.server.ServerInfo; import de.themoep.minedown.adventure.MineDown; @@ -15,7 +15,8 @@ import net.william278.velocitab.config.Placeholder; import net.william278.velocitab.player.TabPlayer; import org.jetbrains.annotations.NotNull; -import java.util.Optional; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.TimeUnit; @@ -32,6 +33,8 @@ public class PlayerTabList { @Subscribe public void onPlayerJoin(@NotNull ServerPostConnectEvent event) { final Player joined = event.getPlayer(); + plugin.getScoreboardManager().resetCache(joined); + // Remove the player from the tracking list if they are switching servers if (event.getPreviousServer() == null) { players.removeIf(player -> player.getPlayer().getUniqueId().equals(joined.getUniqueId())); @@ -46,14 +49,61 @@ public class PlayerTabList { } // Add the player to the tracking list - players.add(plugin.getTabPlayer(joined)); + final TabPlayer tabPlayer = plugin.getTabPlayer(joined); + players.add(tabPlayer); - // Update the tab list of all players - plugin.getServer().getScheduler().buildTask(plugin, this::updateList) + // Update lists + plugin.getServer().getScheduler() + .buildTask(plugin, () -> { + final TabList tabList = joined.getTabList(); + final Map playerRoles = new HashMap<>(); + players.forEach(player -> { + playerRoles.put(player.getPlayer(), tabPlayer.getTeamName()); + tabList.getEntries().stream() + .filter(e -> e.getProfile().getId().equals(player.getPlayer().getUniqueId())).findFirst() + .ifPresentOrElse( + entry -> entry.setDisplayName(player.getDisplayName(plugin)), + () -> tabList.addEntry(createEntry(player, tabList)) + ); + addPlayerToTabList(player, tabPlayer); + player.sendHeaderAndFooter(this); + }); + plugin.getScoreboardManager().sendTeamPackets(joined, playerRoles); + }) .delay(500, TimeUnit.MILLISECONDS) .schedule(); } + @NotNull + private TabListEntry createEntry(@NotNull TabPlayer player, @NotNull TabList tabList) { + return TabListEntry.builder() + .profile(player.getPlayer().getGameProfile()) + .displayName(player.getDisplayName(plugin)) + .latency(0) + .tabList(tabList) + .build(); + } + + private void addPlayerToTabList(@NotNull TabPlayer player, @NotNull TabPlayer newPlayer) { + if (newPlayer.getPlayer().getUniqueId().equals(player.getPlayer().getUniqueId())) { + return; + } + + player.getPlayer() + .getTabList().getEntries().stream() + .filter(e -> e.getProfile().getId().equals(newPlayer.getPlayer().getUniqueId())).findFirst() + .ifPresentOrElse( + entry -> entry.setDisplayName(newPlayer.getDisplayName(plugin)), + () -> player.getPlayer().getTabList() + .addEntry(createEntry(newPlayer, player.getPlayer().getTabList())) + ); + plugin.getScoreboardManager().updateRoles( + player.getPlayer(), + newPlayer.getTeamName(), + newPlayer.getPlayer().getUsername() + ); + } + @Subscribe public void onPlayerQuit(@NotNull DisconnectEvent event) { // Remove the player from the tracking list @@ -63,56 +113,26 @@ public class PlayerTabList { plugin.getServer().getAllPlayers().forEach(player -> player.getTabList().removeEntry(event.getPlayer().getUniqueId())); // Update the tab list of all players - plugin.getServer().getScheduler().buildTask(plugin, () -> { - plugin.getScoreboardManager().removeTeam(event.getPlayer()); - updateList(); - }) - .delay(500, TimeUnit.MILLISECONDS) - .schedule(); - } - - public void updatePlayer(@NotNull TabPlayer tabPlayer) { plugin.getServer().getScheduler() - .buildTask(plugin, () -> { - // Update the player's team sorting - players.remove(tabPlayer); - players.add(tabPlayer); - - plugin.getScoreboardManager().setPlayerTeam(tabPlayer); - - updateList(); - }) + .buildTask(plugin, () -> players.forEach(player -> { + player.getPlayer().getTabList().removeEntry(event.getPlayer().getUniqueId()); + player.sendHeaderAndFooter(this); + })) .delay(500, TimeUnit.MILLISECONDS) .schedule(); } - private void updateList() { - final ImmutableList players = ImmutableList.copyOf(this.players); - players.forEach(player -> { - player.sendHeaderAndFooter(this); - - // Fill the tab list with the players - players.forEach(listedPlayer -> { - final Optional current = player.getPlayer().getTabList().getEntries().stream() - .filter(entry -> entry.getProfile().getId().equals(listedPlayer.getPlayer().getUniqueId())) - .findFirst(); - current.ifPresentOrElse( - entry -> entry.setDisplayName(listedPlayer.getDisplayName(plugin)), - () -> player.getPlayer().getTabList().addEntry(TabListEntry.builder() - .profile(listedPlayer.getPlayer().getGameProfile()) - .displayName(listedPlayer.getDisplayName(plugin)) - .latency(0) - .tabList(player.getPlayer().getTabList()) - .build())); - plugin.getScoreboardManager().setPlayerTeam(listedPlayer); - }); - - // Remove players in the tab list that are not in the players list - player.getPlayer().getTabList().getEntries().stream() - .filter(entry -> players.stream() - .noneMatch(listedPlayer -> listedPlayer.getPlayer().getUniqueId().equals(entry.getProfile().getId()))) - .forEach(entry -> player.getPlayer().getTabList().removeEntry(entry.getProfile().getId())); - }); + public void onPlayerRoleUpdate(@NotNull TabPlayer tabPlayer) { + plugin.getServer().getScheduler() + .buildTask(plugin, () -> players.forEach(player -> { + player.getPlayer().getTabList().getEntries().stream() + .filter(e -> e.getProfile().getId().equals(tabPlayer.getPlayer().getUniqueId())).findFirst() + .ifPresent(entry -> entry.setDisplayName(tabPlayer.getDisplayName(plugin))); + plugin.getScoreboardManager().updateRoles(player.getPlayer(), + tabPlayer.getTeamName(), tabPlayer.getPlayer().getUsername()); + })) + .delay(500, TimeUnit.MILLISECONDS) + .schedule(); } @NotNull