From 50ae941f8013249a709fd54e902c84d06fbb6a7c Mon Sep 17 00:00:00 2001 From: foxikle Date: Mon, 8 Jul 2024 09:11:10 -0400 Subject: [PATCH 01/10] Change spacing with rank prefixes --- .../cytosis/commands/AllChatCommand.java | 2 +- .../cytosis/events/ServerEventListeners.java | 4 +--- .../net/cytonic/cytosis/ranks/PlayerRank.java | 20 +++++++++---------- .../cytonic/cytosis/ranks/RankManager.java | 6 +++--- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/main/java/net/cytonic/cytosis/commands/AllChatCommand.java b/src/main/java/net/cytonic/cytosis/commands/AllChatCommand.java index ce8cb50d..72e10547 100644 --- a/src/main/java/net/cytonic/cytosis/commands/AllChatCommand.java +++ b/src/main/java/net/cytonic/cytosis/commands/AllChatCommand.java @@ -5,6 +5,7 @@ import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.arguments.ArgumentType; import net.minestom.server.entity.Player; + import static net.cytonic.cytosis.utils.MiniMessageTemplate.MM; /** @@ -29,7 +30,6 @@ public AllChatCommand() { if (sender instanceof final Player player) { Component message = Component.text("") .append(Cytosis.getRankManager().getPlayerRank(player.getUuid()).orElseThrow().getPrefix()) - .appendSpace() .append(Component.text(player.getUsername(), (Cytosis.getRankManager().getPlayerRank(player.getUuid()).orElseThrow().getTeamColor()))) .append(Component.text(":", Cytosis.getRankManager().getPlayerRank(player.getUuid()).orElseThrow().getChatColor())) .appendSpace() diff --git a/src/main/java/net/cytonic/cytosis/events/ServerEventListeners.java b/src/main/java/net/cytonic/cytosis/events/ServerEventListeners.java index 797bd372..467a1915 100644 --- a/src/main/java/net/cytonic/cytosis/events/ServerEventListeners.java +++ b/src/main/java/net/cytonic/cytosis/events/ServerEventListeners.java @@ -92,7 +92,7 @@ public static void initServerEvents() { if (player.hasPermission(STR."cytonic.chat.\{channel.name().toLowerCase()}")) { sendMessage(originalMessage, channel, player); } else { - player.sendMessage(MM."Whoops! It looks like you can't chat in the \{channel.name().toLowerCase()} channel. \uD83E\uDD14"); + player.sendMessage(MM."Whoops! It looks like you can't chat in the \{channel.name().toLowerCase()} channel. \uD83E\uDD14"); Cytosis.getChatManager().setChannel(player.getUuid(), ChatChannel.ALL); } break; @@ -138,7 +138,6 @@ private static void sendMessage(String originalMessage, ChatChannel channel, Pla Component message = Component.text("") .append(channel.getPrefix()) .append(Cytosis.getRankManager().getPlayerRank(player.getUuid()).orElseThrow().getPrefix()) - .appendSpace() .append(Component.text(player.getUsername(), (Cytosis.getRankManager().getPlayerRank(player.getUuid()).orElseThrow().getTeamColor()))) .append(Component.text(":", Cytosis.getRankManager().getPlayerRank(player.getUuid()).orElseThrow().getChatColor())) .appendSpace() @@ -147,7 +146,6 @@ private static void sendMessage(String originalMessage, ChatChannel channel, Pla } else { Component message = Component.text("") .append(Cytosis.getRankManager().getPlayerRank(player.getUuid()).orElseThrow().getPrefix()) - .appendSpace() .append(Component.text(player.getUsername(), (Cytosis.getRankManager().getPlayerRank(player.getUuid()).orElseThrow().getTeamColor()))) .append(Component.text(":", Cytosis.getRankManager().getPlayerRank(player.getUuid()).orElseThrow().getChatColor())) .appendSpace() diff --git a/src/main/java/net/cytonic/cytosis/ranks/PlayerRank.java b/src/main/java/net/cytonic/cytosis/ranks/PlayerRank.java index 684979b5..83c57316 100644 --- a/src/main/java/net/cytonic/cytosis/ranks/PlayerRank.java +++ b/src/main/java/net/cytonic/cytosis/ranks/PlayerRank.java @@ -15,45 +15,45 @@ public enum PlayerRank { /** * The [OWNER] rank */ - OWNER(MM."[OWNER]", NamedTextColor.RED, NamedTextColor.WHITE, new String[]{"*"}), + OWNER(MM."[OWNER] ", NamedTextColor.RED, NamedTextColor.WHITE, new String[]{"*"}), /** * The [ADMIN] rank */ - ADMIN(MM."[ADMIN]", NamedTextColor.RED, NamedTextColor.WHITE, new String[]{"*"}), + ADMIN(MM."[ADMIN] ", NamedTextColor.RED, NamedTextColor.WHITE, new String[]{"*"}), /** * The [MOD] rank */ - MODERATOR(MM."[MOD]", NamedTextColor.GREEN, NamedTextColor.WHITE, new String[]{"cytonic.chat.staff", "cytosis.commands.fly"}), + MODERATOR(MM."[MOD] ", NamedTextColor.GREEN, NamedTextColor.WHITE, new String[]{"cytonic.chat.staff", "cytosis.commands.fly"}), /** * The [HELPER] rank */ - HELPER(MM."[HELPER]", NamedTextColor.AQUA, NamedTextColor.WHITE, new String[]{"cytonic.chat.staff", "cytosis.commands.fly"}), + HELPER(MM."[HELPER] ", NamedTextColor.AQUA, NamedTextColor.WHITE, new String[]{"cytonic.chat.staff", "cytosis.commands.fly"}), // player ranks /** * The [ELYSIAN] rank */ - ELYSIAN(MM."[ELYSIAN]", NamedTextColor.GOLD, NamedTextColor.WHITE, new String[]{}), + ELYSIAN(MM."[ELYSIAN] ", NamedTextColor.GOLD, NamedTextColor.WHITE, new String[]{}), /** * The [CELESTIAL] rank */ - CELESTIAL(MM."[CELESTIAL]", NamedTextColor.DARK_AQUA, NamedTextColor.WHITE, new String[]{}), + CELESTIAL(MM."[CELESTIAL] ", NamedTextColor.DARK_AQUA, NamedTextColor.WHITE, new String[]{}), /** * The [MASTER] rank */ - MASTER(MM."[MASTER]", NamedTextColor.DARK_RED, NamedTextColor.WHITE, new String[]{}), + MASTER(MM."[MASTER] ", NamedTextColor.DARK_RED, NamedTextColor.WHITE, new String[]{}), /** * The [VALIENT] rank */ - VALIENT(MM."[VALIENT]", NamedTextColor.DARK_GREEN, NamedTextColor.WHITE, new String[]{}), + VALIENT(MM."[VALIENT] ", NamedTextColor.DARK_GREEN, NamedTextColor.WHITE, new String[]{}), /** * The [NOBLE] rank */ - NOBLE(MM."[NOBLE]", NamedTextColor.DARK_PURPLE, NamedTextColor.WHITE, new String[]{}), + NOBLE(MM."[NOBLE] ", NamedTextColor.DARK_PURPLE, NamedTextColor.WHITE, new String[]{}), /** * The [DEFAULT] rank */ - DEFAULT(MM."[DEFAULT]", NamedTextColor.GRAY, NamedTextColor.GRAY, new String[]{}); + DEFAULT(MM."", NamedTextColor.GRAY, NamedTextColor.GRAY, new String[]{}); private final Component prefix; private final NamedTextColor teamColor; diff --git a/src/main/java/net/cytonic/cytosis/ranks/RankManager.java b/src/main/java/net/cytonic/cytosis/ranks/RankManager.java index e2ca12ee..fc743a30 100644 --- a/src/main/java/net/cytonic/cytosis/ranks/RankManager.java +++ b/src/main/java/net/cytonic/cytosis/ranks/RankManager.java @@ -40,7 +40,7 @@ public void init() { Team team = new TeamBuilder(value.ordinal() + value.name(), MinecraftServer.getTeamManager()) .collisionRule(TeamsPacket.CollisionRule.NEVER) .teamColor(value.getTeamColor()) - .prefix(value.getPrefix().appendSpace()) + .prefix(value.getPrefix()) .build(); teamMap.put(value, team); } @@ -84,14 +84,14 @@ public void changeRank(Player player, PlayerRank rank) { } /** - * Sets up the cosmetics. (Team, tab list, etc) + * Sets up the cosmetics. (Team, tab list, etc.) * @param player The player * @param rank The rank */ private void setupCosmetics(Player player, PlayerRank rank) { addPermissions(player, rank.getPermissions()); teamMap.get(rank).addMember(player.getUsername()); - player.setCustomName(rank.getPrefix().appendSpace().append(player.getName())); + player.setCustomName(rank.getPrefix().append(player.getName())); Cytosis.getCommandHandler().recalculateCommands(player); } From 4e78cf55a6c967f37bc67998a8474924cd58d865 Mon Sep 17 00:00:00 2001 From: foxikle Date: Mon, 8 Jul 2024 09:20:55 -0400 Subject: [PATCH 02/10] Create an interface to model a creator after --- .../cytosis/playerlist/PlayerlistCreator.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/main/java/net/cytonic/cytosis/playerlist/PlayerlistCreator.java diff --git a/src/main/java/net/cytonic/cytosis/playerlist/PlayerlistCreator.java b/src/main/java/net/cytonic/cytosis/playerlist/PlayerlistCreator.java new file mode 100644 index 00000000..45540412 --- /dev/null +++ b/src/main/java/net/cytonic/cytosis/playerlist/PlayerlistCreator.java @@ -0,0 +1,37 @@ +package net.cytonic.cytosis.playerlist; + +import net.kyori.adventure.text.Component; +import net.minestom.server.entity.Player; + +import java.util.List; + +public interface PlayerlistCreator { + + /** + * Creates all the categories for the playerlist + * + * @return The list of categories + */ + List createColumns(Player player); + + /** + * Creates the header for the playerlist + * + * @return The header in Component form + */ + Component header(Player player); + + /** + * creates the footer for the playerlist + * + * @return The footer in Component form + */ + Component footer(Player player); + + /** + * Gets the number of columns, between one and 4, inclusive. + * + * @return A number between 1 and 4 [1, 4] + */ + int getColumnCount(); +} From 08bf3381b7b22fe13bfccfd22f335bb81faea4cd Mon Sep 17 00:00:00 2001 From: foxikle Date: Mon, 8 Jul 2024 09:21:13 -0400 Subject: [PATCH 03/10] Delete categories as they have been replaced by Column --- .../playerlist/PlayerListCategory.java | 43 ------------------- 1 file changed, 43 deletions(-) delete mode 100644 src/main/java/net/cytonic/cytosis/playerlist/PlayerListCategory.java diff --git a/src/main/java/net/cytonic/cytosis/playerlist/PlayerListCategory.java b/src/main/java/net/cytonic/cytosis/playerlist/PlayerListCategory.java deleted file mode 100644 index 7afee988..00000000 --- a/src/main/java/net/cytonic/cytosis/playerlist/PlayerListCategory.java +++ /dev/null @@ -1,43 +0,0 @@ -package net.cytonic.cytosis.playerlist; - -import lombok.Getter; -import lombok.Setter; -import net.kyori.adventure.text.Component; - -import java.util.Comparator; -import java.util.List; - -/** - * A class that holds data about a category in the player list - */ -@Getter -@Setter -public class PlayerListCategory { - private Component name; - private PlayerListFavicon favicon; - private int priority; - private boolean enabled; - private List entries; // - - /** - * Sort the entries in the category by priority - */ - public void sortEntries() { - entries.sort(Comparator.comparingInt(PlayerListEntry::getPriority)); - } - - /** - * Creates a new player list category - * - * @param name The name of the category - * @param favicon The {@link PlayerListFavicon} of the category - * @param priority The ordering of the category - * @param entries The {@link PlayerListEntry}s in the category - */ - public PlayerListCategory(Component name, PlayerListFavicon favicon, int priority, List entries) { - this.name = name; - this.favicon = favicon; - this.priority = priority; - this.entries = entries; - } -} From 7139fc3e8d2944b3bcc3bcb4902208365863ef30 Mon Sep 17 00:00:00 2001 From: foxikle Date: Mon, 8 Jul 2024 09:29:31 -0400 Subject: [PATCH 04/10] Create column object --- .../cytonic/cytosis/playerlist/Column.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/java/net/cytonic/cytosis/playerlist/Column.java diff --git a/src/main/java/net/cytonic/cytosis/playerlist/Column.java b/src/main/java/net/cytonic/cytosis/playerlist/Column.java new file mode 100644 index 00000000..cdd147c5 --- /dev/null +++ b/src/main/java/net/cytonic/cytosis/playerlist/Column.java @@ -0,0 +1,53 @@ +package net.cytonic.cytosis.playerlist; + +import lombok.Getter; +import lombok.Setter; +import net.kyori.adventure.text.Component; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * A class representing one column in the player list. Columns are used to display data in the player list, holding up + * to 19 entries. If a column has more than 20 entries, they will be ignored. + */ +@Getter +@Setter +public class Column { + private Component name; + private PlayerListFavicon favicon; + private List entries; // + + /** + * Creates a new column with the specified name, favicon and entries + * + * @param name the title of the column, shown at the top + * @param favicon the favicon, or player head texture shown at on the top row, next to the name. + * @param entries the entries in the column, with a max of 19. Entries over 19 are ignored + */ + public Column(Component name, PlayerListFavicon favicon, List entries) { + this.name = name; + this.favicon = favicon; + this.entries = entries; + } + + /** + * Creates a new column with the specified name and favicon. The entries are set to an empty list + * + * @param name the title of the column, shown at the top. + * @param favicon the favicon, or player head texture shown at on the top row, next to the name. + */ + public Column(Component name, PlayerListFavicon favicon) { + this.name = name; + this.favicon = favicon; + this.entries = new ArrayList<>(); + } + + /** + * Sorts the entries by priority + */ + public void sortEntries() { + entries.sort(Comparator.comparingInt(PlayerListEntry::getPriority)); + } +} From 217f4647e1c6dd0724800af291af0387d3af56e7 Mon Sep 17 00:00:00 2001 From: foxikle Date: Mon, 8 Jul 2024 09:29:52 -0400 Subject: [PATCH 05/10] Create a default implementation of the scoreboard provider --- .../playerlist/DefaultPlayerListCreator.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/main/java/net/cytonic/cytosis/playerlist/DefaultPlayerListCreator.java diff --git a/src/main/java/net/cytonic/cytosis/playerlist/DefaultPlayerListCreator.java b/src/main/java/net/cytonic/cytosis/playerlist/DefaultPlayerListCreator.java new file mode 100644 index 00000000..4b6af058 --- /dev/null +++ b/src/main/java/net/cytonic/cytosis/playerlist/DefaultPlayerListCreator.java @@ -0,0 +1,84 @@ +package net.cytonic.cytosis.playerlist; + +import net.cytonic.cytosis.Cytosis; +import net.cytonic.cytosis.ranks.PlayerRank; +import net.cytonic.cytosis.utils.DurationParser; +import net.cytonic.cytosis.utils.Utils; +import net.kyori.adventure.text.Component; +import net.minestom.server.entity.Player; +import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +import static net.cytonic.cytosis.utils.MiniMessageTemplate.MM; + +public class DefaultPlayerListCreator implements PlayerlistCreator { + private final int colCount = 4; + private final Instant start = Instant.now(); + + @Override + public List createColumns(Player player) { + List columns = new ArrayList<>(); + + List players = new ArrayList<>(); + + for (Player p : Cytosis.getOnlinePlayers()) { + PlayerRank rank = Cytosis.getRankManager().getPlayerRank(p.getUuid()).orElse(PlayerRank.DEFAULT); + players.add(new PlayerListEntry(rank.getPrefix().append(p.getName()), rank.ordinal(), + new PlayerInfoUpdatePacket.Property("textures", p.getSkin().textures(), p.getSkin().signature()))); + } + + Column players1 = new Column(MM." Players ", PlayerListFavicon.PURPLE); + if (players.size() >= 19) { + players1.setEntries(new ArrayList<>(players.subList(0, 19))); + players = new ArrayList<>(players.subList(19, players.size())); + } else { + players1.setEntries(new ArrayList<>(players)); + players.clear(); + } + + + Column players2 = new Column(MM." Players ", PlayerListFavicon.PURPLE); + if (players.size() >= 19) { + int extra = players.size() - 19; + players = new ArrayList<>(players.subList(0, 18)); + players.add(new PlayerListEntry(MM." + \{extra} more", 100)); + } + players2.setEntries(players); + columns.add(players1); + columns.add(players2); + + columns.add(new Column(MM." Server Info", PlayerListFavicon.BLUE, + Utils.list(new PlayerListEntry(MM."Uptime: \{DurationParser.unparse(start, " ")}", 0), + new PlayerListEntry(Component.empty(), 1), + new PlayerListEntry(MM."Players: \{Cytosis.getOnlinePlayers().size()}", 2), + new PlayerListEntry(MM."Version: \{Cytosis.VERSION}", 3), + new PlayerListEntry(MM."ID: \{Cytosis.getRawID()}", 4), + new PlayerListEntry(MM."Network Players: \{Cytosis.getCytonicNetwork().getNetworkPlayers().size()}", 5) + ))); + columns.add(new Column(MM." Player Info", PlayerListFavicon.YELLOW, Utils.list( + new PlayerListEntry(MM."Rank: \{Cytosis.getRankManager().getPlayerRank(player.getUuid()).orElse(PlayerRank.DEFAULT).name()}", 0), + new PlayerListEntry(MM."Ping: \{player.getLatency()}ms", 1), + new PlayerListEntry(MM."Locale: \{player.getLocale()}", 2) + ))); + + return columns; + } + + @Override + public Component header(Player player) { + return MM."CytonicMC"; + } + + @Override + public Component footer(Player player) { + return MM."mc.cytonic.net".appendNewline().append(MM."forums.cytonic.net"); + } + + @Override + public int getColumnCount() { + return colCount; + } +} From 3134a43f4a27fd215395b4fa1b511dd0833f4091 Mon Sep 17 00:00:00 2001 From: foxikle Date: Mon, 8 Jul 2024 09:30:16 -0400 Subject: [PATCH 06/10] Fix duration parser to support negative durations --- .../java/net/cytonic/cytosis/utils/DurationParser.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/cytonic/cytosis/utils/DurationParser.java b/src/main/java/net/cytonic/cytosis/utils/DurationParser.java index 470076ca..051bdef1 100644 --- a/src/main/java/net/cytonic/cytosis/utils/DurationParser.java +++ b/src/main/java/net/cytonic/cytosis/utils/DurationParser.java @@ -61,11 +61,11 @@ public static String unparse(@Nullable Instant instant, String spacing) { if (instant == null) return null; Duration duration = Duration.between(Instant.now(), instant); - long years = duration.toDays() / 365; - long days = duration.toDays() % 365; - long hours = duration.toHours() % 24; - long minutes = duration.toMinutes() % 60; - long seconds = duration.getSeconds() % 60; + long years = Math.abs(duration.toDays() / 365); + long days = Math.abs(duration.toDays() % 365); + long hours = Math.abs(duration.toHours() % 24); + long minutes = Math.abs(duration.toMinutes() % 60); + long seconds = Math.abs(duration.getSeconds() % 60); StringBuilder builder = new StringBuilder(); From 786c1b2544e5fe155fcc3cf6dc8a624155500d9a Mon Sep 17 00:00:00 2001 From: foxikle Date: Mon, 8 Jul 2024 09:30:44 -0400 Subject: [PATCH 07/10] Add a constructor to create an entry with a custom favicon --- .../cytosis/playerlist/PlayerListEntry.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/cytonic/cytosis/playerlist/PlayerListEntry.java b/src/main/java/net/cytonic/cytosis/playerlist/PlayerListEntry.java index a8f50be7..0c3c99ca 100644 --- a/src/main/java/net/cytonic/cytosis/playerlist/PlayerListEntry.java +++ b/src/main/java/net/cytonic/cytosis/playerlist/PlayerListEntry.java @@ -3,6 +3,7 @@ import lombok.Getter; import lombok.Setter; import net.kyori.adventure.text.Component; +import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket; /** * A class representing one line in the player list @@ -11,7 +12,7 @@ @Setter public class PlayerListEntry { private Component name; - private PlayerListFavicon favicon; + private PlayerInfoUpdatePacket.Property favicon; private int priority; /** @@ -23,7 +24,7 @@ public class PlayerListEntry { */ public PlayerListEntry(Component name, PlayerListFavicon favicon, int priority) { this.name = name; - this.favicon = favicon; + this.favicon = favicon.getProperty(); this.priority = priority; } @@ -34,7 +35,20 @@ public PlayerListEntry(Component name, PlayerListFavicon favicon, int priority) */ public PlayerListEntry(Component name, int priority) { this.name = name; - this.favicon = PlayerListFavicon.GREY; + this.favicon = PlayerListFavicon.GREY.getProperty(); + this.priority = priority; + } + + /** + * Creates a new player list entry with the favicon provided + * + * @param name The name of the entry + * @param priority The ordering of the entry + * @param favicon Effectively the {@link PlayerListFavicon} of the entry, but its a {@link PlayerInfoUpdatePacket.Property} + */ + public PlayerListEntry(Component name, int priority, PlayerInfoUpdatePacket.Property favicon) { + this.name = name; + this.favicon = favicon; this.priority = priority; } @@ -45,7 +59,7 @@ public PlayerListEntry(Component name, int priority) { */ public PlayerListEntry(String name, int priority) { this.name = Component.text(name); - this.favicon = PlayerListFavicon.GREY; + this.favicon = PlayerListFavicon.GREY.getProperty(); this.priority = priority; } @@ -57,7 +71,7 @@ public PlayerListEntry(String name, int priority) { */ public PlayerListEntry(String name, PlayerListFavicon favicon, int priority) { this.name = Component.text(name); - this.favicon = favicon; + this.favicon = favicon.getProperty(); this.priority = priority; } } From cbe47c4d9b99718ab7d8c7da804dc2bbf66a9762 Mon Sep 17 00:00:00 2001 From: foxikle Date: Mon, 8 Jul 2024 09:31:10 -0400 Subject: [PATCH 08/10] new tab list api --- src/main/java/net/cytonic/cytosis/Cytosis.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/net/cytonic/cytosis/Cytosis.java b/src/main/java/net/cytonic/cytosis/Cytosis.java index 538771c5..9522d923 100644 --- a/src/main/java/net/cytonic/cytosis/Cytosis.java +++ b/src/main/java/net/cytonic/cytosis/Cytosis.java @@ -11,9 +11,6 @@ import net.cytonic.cytosis.logging.Logger; import net.cytonic.cytosis.managers.*; import net.cytonic.cytosis.messaging.MessagingManager; -import net.cytonic.cytosis.playerlist.PlayerListCategory; -import net.cytonic.cytosis.playerlist.PlayerListEntry; -import net.cytonic.cytosis.playerlist.PlayerListFavicon; import net.cytonic.cytosis.plugins.PluginManager; import net.cytonic.cytosis.ranks.RankManager; import net.cytonic.cytosis.utils.Utils; From 9c7154699eeed7c486c054ab1edbe2eca16a383e Mon Sep 17 00:00:00 2001 From: foxikle Date: Mon, 8 Jul 2024 09:31:37 -0400 Subject: [PATCH 09/10] overhaul tab list, making it update on updateInterval ticks --- .../cytosis/managers/PlayerListManager.java | 224 ++++++++++++++---- 1 file changed, 180 insertions(+), 44 deletions(-) diff --git a/src/main/java/net/cytonic/cytosis/managers/PlayerListManager.java b/src/main/java/net/cytonic/cytosis/managers/PlayerListManager.java index 03022088..dc492832 100644 --- a/src/main/java/net/cytonic/cytosis/managers/PlayerListManager.java +++ b/src/main/java/net/cytonic/cytosis/managers/PlayerListManager.java @@ -3,40 +3,49 @@ import lombok.Getter; import lombok.Setter; import net.cytonic.cytosis.Cytosis; -import net.cytonic.cytosis.playerlist.PlayerListCategory; -import net.cytonic.cytosis.playerlist.PlayerListEntry; +import net.cytonic.cytosis.playerlist.*; import net.kyori.adventure.text.Component; +import net.minestom.server.MinecraftServer; import net.minestom.server.entity.GameMode; import net.minestom.server.entity.Player; -import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.SendablePacket; +import net.minestom.server.network.packet.server.play.PlayerInfoRemovePacket; import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket; +import net.minestom.server.timer.TaskSchedule; import net.minestom.server.utils.PacketUtils; import java.util.*; - -import static net.cytonic.cytosis.utils.MiniMessageTemplate.MM; +import java.util.concurrent.ConcurrentHashMap; /** * A class that manages the player list */ @Setter @Getter +@SuppressWarnings("UnstableApiUsage") public class PlayerListManager { + private final UUID[][] listUUIDs; // + private final Map playerComponents = new ConcurrentHashMap<>(); + private final Map playerFavicons = new ConcurrentHashMap<>(); + private PlayerlistCreator creator; + // in ticks + private int updateInterval = 20; + /** * The default player list manager constructor */ public PlayerListManager() { - // do nothing + creator = new DefaultPlayerListCreator(); + scheduleUpdate(); + listUUIDs = new UUID[creator.getColumnCount()][20]; + for (int i = 0; i < listUUIDs.length; i++) { + for (int j = 0; j < listUUIDs[i].length; j++) { + listUUIDs[i][j] = UUID.randomUUID(); + } + } } - //todo make it per player? - private List globalCategories = new ArrayList<>(); - - // Header and Footer - @Setter private static Component header = MM."CytonicMC"; - @Setter private static Component footer = MM."mc.cytonic.net"; - /** * Injects the player list packets into the player's connection * @@ -44,7 +53,7 @@ public PlayerListManager() { */ public void setupPlayer(Player player) { // setup tab header & footer - player.sendPlayerListHeaderAndFooter(header, footer); + player.sendPlayerListHeaderAndFooter(creator.header(player), creator.footer(player)); // remove them from the player list for everyone, but keep skin data PacketUtils.broadcastPlayPacket(new PlayerInfoUpdatePacket( @@ -56,10 +65,7 @@ public void setupPlayer(Player player) { // remove everyone from the player - - for (ServerPacket packet : createPackets()) { - player.sendPacket(packet); - } + player.sendPackets(createInjectPackets(player)); } /** @@ -67,44 +73,174 @@ public void setupPlayer(Player player) { * * @return the list of packets */ - private List createPackets() { - globalCategories.sort(Comparator.comparingInt(PlayerListCategory::getPriority)); - List packets = new ArrayList<>(); - - char currentLetter = 'A'; - - for (PlayerListCategory category : globalCategories) { // category naming is "A0",then the next category is B0, etc. - category.sortEntries(); + private List createInjectPackets(Player player) { + List packets = new ArrayList<>(); + if (!playerComponents.containsKey(player.getUuid())) { + Component[][] components = new Component[creator.getColumnCount()][20]; + for (int i = 0; i < components.length; i++) { + for (int j = 0; j < components[i].length; j++) { + components[i][j] = Component.empty(); + } + } + playerComponents.put(player.getUuid(), components); + } - char categoryletter = 'a'; - UUID categoryUUID = UUID.randomUUID(); + if (!playerFavicons.containsKey(player.getUuid())) { + PlayerInfoUpdatePacket.Property[][] favicons = new PlayerInfoUpdatePacket.Property[creator.getColumnCount()][20]; + for (PlayerInfoUpdatePacket.Property[] favicon : favicons) { + Arrays.fill(favicon, PlayerListFavicon.GREY.getProperty()); + } + playerFavicons.put(player.getUuid(), favicons); + } - PlayerInfoUpdatePacket categoryPacket = new PlayerInfoUpdatePacket( - EnumSet.of(PlayerInfoUpdatePacket.Action.ADD_PLAYER, PlayerInfoUpdatePacket.Action.UPDATE_LISTED, PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME), - List.of(new PlayerInfoUpdatePacket.Entry(categoryUUID, STR."!\{currentLetter}-a", category.getFavicon().property(), true, 1, GameMode.CREATIVE, category.getName(), null))); - packets.add(categoryPacket); - for (PlayerListEntry entry : category.getEntries()) { - UUID entryUUID = UUID.randomUUID(); - PlayerInfoUpdatePacket entryPacket = new PlayerInfoUpdatePacket( + for (int i = 0; i < listUUIDs.length; i++) { + char col = (char) ('A' + i); + for (int j = 0; j < listUUIDs[i].length; j++) { + char row = (char) ('a' + j); + UUID uuid = listUUIDs[i][j]; + packets.add(new PlayerInfoUpdatePacket( EnumSet.of(PlayerInfoUpdatePacket.Action.ADD_PLAYER, PlayerInfoUpdatePacket.Action.UPDATE_LISTED, PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME), - List.of(new PlayerInfoUpdatePacket.Entry(entryUUID, STR."!\{currentLetter}-\{categoryletter++}", - List.of(entry.getFavicon().getProperty()), true, 1, - GameMode.CREATIVE, entry.getName(), null))); - packets.add(entryPacket); + List.of(new PlayerInfoUpdatePacket.Entry(uuid, STR."!\{col}-\{row}", List.of(playerFavicons.get(player.getUuid())[i][j]), + true, 1, GameMode.CREATIVE, playerComponents.get(player.getUuid())[i][j], null) + ))); } - currentLetter++; } // remove online players too - for (Player player : Cytosis.getOnlinePlayers()) { + for (Player p : Cytosis.getOnlinePlayers()) { packets.add(new PlayerInfoUpdatePacket( PlayerInfoUpdatePacket.Action.UPDATE_LISTED, - new PlayerInfoUpdatePacket.Entry(player.getUuid(), player.getUsername(), List.of( - new PlayerInfoUpdatePacket.Property("textures", player.getSkin().textures(), player.getSkin().signature()) - ), false, player.getLatency(), player.getGameMode(), player.getDisplayName(), null) + new PlayerInfoUpdatePacket.Entry(p.getUuid(), p.getUsername(), List.of( + new PlayerInfoUpdatePacket.Property("textures", p.getSkin().textures(), p.getSkin().signature()) + ), false, p.getLatency(), p.getGameMode(), p.getDisplayName(), null) )); } return packets; } + + public void update(Player player) { + List columns = creator.createColumns(player); + if (columns.size() != creator.getColumnCount()) + throw new IllegalArgumentException("Column count does not match size of column list"); + + columns.forEach(Column::sortEntries); + PlayerInfoUpdatePacket.Property[][] updatedFavicons = toFavicons(new ArrayList<>(columns)); + PlayerInfoUpdatePacket.Property[][] favicons = playerFavicons.getOrDefault(player.getUuid(), updatedFavicons); + playerFavicons.put(player.getUuid(), updatedFavicons); + Component[][] updatedComponents = toComponents(columns); + Component[][] components = playerComponents.getOrDefault(player.getUuid(), updatedComponents); + playerComponents.put(player.getUuid(), updatedComponents); + + + List updatePackets = new ArrayList<>(); + + for (int i = 0; i < updatedComponents.length; i++) { + if (faviconNotEquals(favicons[i][0], updatedFavicons[i][0])) { + updatePackets.add(new PlayerInfoRemovePacket(listUUIDs[i][0])); + updatePackets.add(new PlayerInfoUpdatePacket( + EnumSet.of(PlayerInfoUpdatePacket.Action.ADD_PLAYER, PlayerInfoUpdatePacket.Action.UPDATE_LISTED, PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME), + List.of(new PlayerInfoUpdatePacket.Entry(listUUIDs[i][0], STR."!\{(char) ('A' + i)}-\{(char) ('a')}", + List.of(updatedFavicons[i][0]), true, 1, GameMode.CREATIVE, updatedComponents[i][0], null) + ))); + } + + if (!components[i][0].equals(columns.get(i).getName())) { + updatePackets.add(new PlayerInfoUpdatePacket( + PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, new PlayerInfoUpdatePacket.Entry(listUUIDs[i][0], STR."!\{(char) ('A' + i)}-\{(char) ('a')}", + List.of(updatedFavicons[i][0]), true, 1, GameMode.CREATIVE, updatedComponents[i][0], null) + )); + } + for (int j = 1; j < updatedComponents[i].length; j++) { + if (faviconNotEquals(favicons[i][j], updatedFavicons[i][j])) { + updatePackets.add(new PlayerInfoRemovePacket(listUUIDs[i][j])); + updatePackets.add(new PlayerInfoUpdatePacket( + EnumSet.of(PlayerInfoUpdatePacket.Action.ADD_PLAYER, PlayerInfoUpdatePacket.Action.UPDATE_LISTED, PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME), + List.of(new PlayerInfoUpdatePacket.Entry(listUUIDs[i][j], STR."!\{(char) ('A' + i)}-\{(char) ('a' + j)}", + List.of(updatedFavicons[i][j]), true, 1, GameMode.CREATIVE, updatedComponents[i][j], null) + ))); + } + if (!components[i][j].equals(updatedComponents[i][j])) { + components[i][j] = updatedComponents[i][j]; + updatePackets.add(new PlayerInfoUpdatePacket( + PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, new PlayerInfoUpdatePacket.Entry(listUUIDs[i][j], STR."!\{(char) ('A' + i)}-\{(char) ('a' + j)}", + List.of(updatedFavicons[i][j]), true, 1, GameMode.CREATIVE, updatedComponents[i][j], null) + )); + } + } + } + + + player.sendPackets(updatePackets); + } + + /** + * Updates all the players tab menus + */ + public void updateAll() { + Cytosis.getOnlinePlayers().forEach(this::update); + } + + + private void scheduleUpdate() { + MinecraftServer.getSchedulerManager().buildTask(() -> { + updateAll(); + scheduleUpdate(); + }).delay(TaskSchedule.tick(updateInterval)).schedule(); + } + + private Component[][] toComponents(List columns) { + Component[][] components = new Component[creator.getColumnCount()][20]; + for (int i = 0; i < components.length; i++) { + components[i][0] = columns.get(i).getName(); + + for (int j = 1; j < components[i].length; j++) { + Column column = columns.get(i); + if (column.getEntries().size() < j) { + components[i][j] = Component.empty(); + continue; + } + components[i][j] = column.getEntries().get(j - 1).getName(); + } + } + return components; + } + + private PlayerInfoUpdatePacket.Property[][] toFavicons(List columns) { + PlayerInfoUpdatePacket.Property[][] favicons = new PlayerInfoUpdatePacket.Property[creator.getColumnCount()][20]; + for (int i = 0; i < favicons.length; i++) { + favicons[i][0] = columns.get(i).getFavicon().getProperty(); + Column column = columns.get(i); + for (int j = 1; j < favicons[i].length; j++) { + List entries = new ArrayList<>(column.getEntries()); + if (entries.size() < j) { + favicons[i][j] = PlayerListFavicon.GREY.getProperty(); + continue; + } + favicons[i][j] = entries.get(j - 1).getFavicon(); + } + } + return favicons; + } + + /** + * A custom equals method for PlayerInfoUpdatePacket.Property + * + * @param p1 the first property + * @param p2 the second property + * @return true if the properties are not equal, indicating an update is needed + */ + private boolean faviconNotEquals(PlayerInfoUpdatePacket.Property p1, PlayerInfoUpdatePacket.Property p2) { + if (!p1.name().equals(p2.name())) { + return true; + } + if (p1.signature() == null || p2.signature() == null) { + return true; + } + if (!p1.signature().equals(p2.signature())) { + return true; + } + return !p1.value().equals(p2.value()); + } + } From 835c9d1f83698f516a2716c2ba7a12b607420cc8 Mon Sep 17 00:00:00 2001 From: foxikle Date: Mon, 8 Jul 2024 09:43:32 -0400 Subject: [PATCH 10/10] update javadocs --- .../cytosis/managers/PlayerListManager.java | 20 ++++++++++++++-- .../playerlist/DefaultPlayerListCreator.java | 23 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/cytonic/cytosis/managers/PlayerListManager.java b/src/main/java/net/cytonic/cytosis/managers/PlayerListManager.java index dc492832..1b3d5f09 100644 --- a/src/main/java/net/cytonic/cytosis/managers/PlayerListManager.java +++ b/src/main/java/net/cytonic/cytosis/managers/PlayerListManager.java @@ -119,6 +119,11 @@ private List createInjectPackets(Player player) { return packets; } + /** + * Update a player's player list + * + * @param player the player to update + */ public void update(Player player) { List columns = creator.createColumns(player); if (columns.size() != creator.getColumnCount()) @@ -181,7 +186,9 @@ public void updateAll() { Cytosis.getOnlinePlayers().forEach(this::update); } - + /** + * Shedules the next update + */ private void scheduleUpdate() { MinecraftServer.getSchedulerManager().buildTask(() -> { updateAll(); @@ -189,6 +196,11 @@ private void scheduleUpdate() { }).delay(TaskSchedule.tick(updateInterval)).schedule(); } + /** + * Converts a list of columns to the components arrays + * @param columns the columns to convert + * @return an array with the components + */ private Component[][] toComponents(List columns) { Component[][] components = new Component[creator.getColumnCount()][20]; for (int i = 0; i < components.length; i++) { @@ -206,6 +218,11 @@ private Component[][] toComponents(List columns) { return components; } + /** + * Converts a list of columns to the favicons + * @param columns the columns to convert + * @return an array with the favicons + */ private PlayerInfoUpdatePacket.Property[][] toFavicons(List columns) { PlayerInfoUpdatePacket.Property[][] favicons = new PlayerInfoUpdatePacket.Property[creator.getColumnCount()][20]; for (int i = 0; i < favicons.length; i++) { @@ -242,5 +259,4 @@ private boolean faviconNotEquals(PlayerInfoUpdatePacket.Property p1, PlayerInfoU } return !p1.value().equals(p2.value()); } - } diff --git a/src/main/java/net/cytonic/cytosis/playerlist/DefaultPlayerListCreator.java b/src/main/java/net/cytonic/cytosis/playerlist/DefaultPlayerListCreator.java index 4b6af058..e66f7ac1 100644 --- a/src/main/java/net/cytonic/cytosis/playerlist/DefaultPlayerListCreator.java +++ b/src/main/java/net/cytonic/cytosis/playerlist/DefaultPlayerListCreator.java @@ -14,10 +14,19 @@ import static net.cytonic.cytosis.utils.MiniMessageTemplate.MM; +/** + * A class providing the default player list for Cytosis + */ public class DefaultPlayerListCreator implements PlayerlistCreator { private final int colCount = 4; private final Instant start = Instant.now(); + /** + * Creates the default player list + * + * @param player the player for personalization + * @return the list of columns whose size is {@code colCount} + */ @Override public List createColumns(Player player) { List columns = new ArrayList<>(); @@ -67,16 +76,30 @@ public List createColumns(Player player) { return columns; } + /** + * Creates the header for the player + * @param player the player for personalization + * @return the component to be displayed as the header + */ @Override public Component header(Player player) { return MM."CytonicMC"; } + /** + * Creates the footer for the player + * @param player the player for personalization + * @return the component to be displayed as the footer + */ @Override public Component footer(Player player) { return MM."mc.cytonic.net".appendNewline().append(MM."forums.cytonic.net"); } + /** + * Gets the column count + * @return the number of columns, between 1 and 4 inclusive + */ @Override public int getColumnCount() { return colCount;