diff --git a/src/main/java/net/minestom/server/Viewable.java b/src/main/java/net/minestom/server/Viewable.java index c93d6a442..53a229dbc 100644 --- a/src/main/java/net/minestom/server/Viewable.java +++ b/src/main/java/net/minestom/server/Viewable.java @@ -1,6 +1,7 @@ package net.minestom.server; import net.kyori.adventure.audience.Audience; +import net.minestom.server.adventure.audience.PacketGroupingAudience; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.utils.PacketUtils; @@ -91,7 +92,7 @@ public interface Viewable { * @return the audience */ default @NotNull Audience getViewersAsAudience() { - return Audience.audience(this.getViewersAsAudiences()); + return PacketGroupingAudience.of(this.getViewers()); } /** diff --git a/src/main/java/net/minestom/server/adventure/BossBarManager.java b/src/main/java/net/minestom/server/adventure/BossBarManager.java index 67e6db2a1..a5fddc022 100644 --- a/src/main/java/net/minestom/server/adventure/BossBarManager.java +++ b/src/main/java/net/minestom/server/adventure/BossBarManager.java @@ -10,10 +10,12 @@ import net.minestom.server.MinecraftServer; import net.minestom.server.entity.Player; import net.minestom.server.event.player.PlayerDisconnectEvent; import net.minestom.server.network.packet.server.play.BossBarPacket; +import net.minestom.server.utils.PacketUtils; import org.jetbrains.annotations.NotNull; import java.util.*; import java.util.function.Consumer; +import java.util.stream.Collectors; import static net.minestom.server.network.packet.server.play.BossBarPacket.Action.*; @@ -50,13 +52,13 @@ public class BossBarManager implements BossBar.Listener { player.getPlayerConnection().sendPacket(holder.createAddPacket()); } } + /** * Removes the specified player from the boss bar's viewers and despawns the boss bar. * * @param player the intended viewer * @param bar the boss bar to hide */ - public void removeBossBar(@NotNull Player player, @NotNull BossBar bar) { Holder holder = this.getOrCreateHandler(bar); @@ -65,6 +67,49 @@ public class BossBarManager implements BossBar.Listener { } } + /** + * Adds the specified players to the boss bar's viewers and spawns the boss bar, registering the + * boss bar if needed. + * + * @param players the players + * @param bar the boss bar + */ + public void addBossBar(@NotNull Collection players, @NotNull BossBar bar) { + Holder holder = this.getOrCreateHandler(bar); + Collection addedPlayers = new ArrayList<>(); + + for (Player player : players) { + if (holder.players.add(player.getUuid())) { + addedPlayers.add(player); + } + } + + if (!addedPlayers.isEmpty()) { + PacketUtils.sendGroupedPacket(players, holder.createAddPacket()); + } + } + + /** + * Removes the specified players from the boss bar's viewers and despawns the boss bar. + * + * @param players the intended viewers + * @param bar the boss bar to hide + */ + public void removeBossBar(@NotNull Collection players, @NotNull BossBar bar) { + Holder holder = this.getOrCreateHandler(bar); + Collection removedPlayers = new ArrayList<>(); + + for (Player player : players) { + if (holder.players.remove(player.getUuid())) { + removedPlayers.add(player); + } + } + + if (!removedPlayers.isEmpty()) { + PacketUtils.sendGroupedPacket(players, holder.createRemovePacket()); + } + } + @Override public void bossBarNameChanged(@NotNull BossBar bar, @NotNull Component oldName, @NotNull Component newName) { Holder holder = this.bars.get(bar); @@ -100,10 +145,11 @@ public class BossBarManager implements BossBar.Listener { * in the connection manager. * * @param packet the packet - * @param players the players + * @param uuids the players */ - private void updatePlayers(BossBarPacket packet, Set players) { - Iterator iterator = players.iterator(); + private void updatePlayers(BossBarPacket packet, Set uuids) { + Iterator iterator = uuids.iterator(); + Collection players = new ArrayList<>(); while (iterator.hasNext()) { Player player = MinecraftServer.getConnectionManager().getPlayer(iterator.next()); @@ -111,9 +157,11 @@ public class BossBarManager implements BossBar.Listener { if (player == null) { iterator.remove(); } else { - player.getPlayerConnection().sendPacket(packet); + players.add(player); } } + + PacketUtils.sendGroupedPacket(players, packet); } /** diff --git a/src/main/java/net/minestom/server/adventure/audience/PacketGroupingAudience.java b/src/main/java/net/minestom/server/adventure/audience/PacketGroupingAudience.java new file mode 100644 index 000000000..66a609dd0 --- /dev/null +++ b/src/main/java/net/minestom/server/adventure/audience/PacketGroupingAudience.java @@ -0,0 +1,108 @@ +package net.minestom.server.adventure.audience; + +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.audience.ForwardingAudience; +import net.kyori.adventure.audience.MessageType; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.sound.SoundStop; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.title.Title; +import net.minestom.server.MinecraftServer; +import net.minestom.server.adventure.AdventurePacketConvertor; +import net.minestom.server.entity.Player; +import net.minestom.server.network.packet.server.play.ChatMessagePacket; +import net.minestom.server.network.packet.server.play.PlayerListHeaderAndFooterPacket; +import net.minestom.server.network.packet.server.play.TitlePacket; +import net.minestom.server.utils.PacketUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +/** + * An audience implementation that sends grouped packets if possible. + */ +public interface PacketGroupingAudience extends ForwardingAudience { + + /** + * Creates a packet grouping audience that wraps a collection of players. + * @param players the players + * @return the audience + */ + static PacketGroupingAudience of(Collection players) { + return new PacketGroupingAudience() { + @Override + public @NotNull Collection getPlayers() { + return players; + } + + @Override + public @NotNull Iterable audiences() { + return players; + } + }; + } + + /** + * Gets an collection of players this audience contains. + * @return the connections + */ + @NotNull Collection getPlayers(); + + @Override + default void sendMessage(@NotNull Identity source, @NotNull Component message, @NotNull MessageType type) { + PacketUtils.sendGroupedPacket(this.getPlayers(), new ChatMessagePacket(message, ChatMessagePacket.Position.fromMessageType(type), source.uuid())); + } + + @Override + default void sendActionBar(@NotNull Component message) { + PacketUtils.sendGroupedPacket(this.getPlayers(), new TitlePacket(TitlePacket.Action.SET_ACTION_BAR, message)); + } + + @Override + default void sendPlayerListHeaderAndFooter(@NotNull Component header, @NotNull Component footer) { + PacketUtils.sendGroupedPacket(this.getPlayers(), new PlayerListHeaderAndFooterPacket(header, footer)); + } + + @Override + default void showTitle(@NotNull Title title) { + PacketUtils.sendGroupedPacket(this.getPlayers(), new TitlePacket(TitlePacket.Action.SET_TITLE, title.title())); + PacketUtils.sendGroupedPacket(this.getPlayers(), new TitlePacket(TitlePacket.Action.SET_SUBTITLE, title.subtitle())); + } + + @Override + default void clearTitle() { + PacketUtils.sendGroupedPacket(this.getPlayers(), new TitlePacket(TitlePacket.Action.HIDE)); + } + + @Override + default void resetTitle() { + PacketUtils.sendGroupedPacket(this.getPlayers(), new TitlePacket(TitlePacket.Action.RESET)); + } + + @Override + default void showBossBar(@NotNull BossBar bar) { + MinecraftServer.getBossBarManager().addBossBar(this.getPlayers(), bar); + } + + @Override + default void hideBossBar(@NotNull BossBar bar) { + MinecraftServer.getBossBarManager().removeBossBar(this.getPlayers(), bar); + } + + @Override + default void playSound(@NotNull Sound sound, double x, double y, double z) { + PacketUtils.sendGroupedPacket(this.getPlayers(), AdventurePacketConvertor.createSoundPacket(sound, x, y, z)); + } + + @Override + default void stopSound(@NotNull SoundStop stop) { + PacketUtils.sendGroupedPacket(this.getPlayers(), AdventurePacketConvertor.createSoundStopPacket(stop)); + } + + @Override + default @NotNull Iterable audiences() { + return this.getPlayers(); + } +} diff --git a/src/main/java/net/minestom/server/instance/Instance.java b/src/main/java/net/minestom/server/instance/Instance.java index 16b3acd61..1cb02b66a 100644 --- a/src/main/java/net/minestom/server/instance/Instance.java +++ b/src/main/java/net/minestom/server/instance/Instance.java @@ -8,6 +8,7 @@ import net.kyori.adventure.audience.Audience; import net.kyori.adventure.audience.ForwardingAudience; import net.minestom.server.MinecraftServer; import net.minestom.server.UpdateManager; +import net.minestom.server.adventure.audience.PacketGroupingAudience; import net.minestom.server.data.Data; import net.minestom.server.data.DataContainer; import net.minestom.server.entity.Entity; @@ -58,7 +59,7 @@ import java.util.function.Consumer; * you need to be sure to signal the {@link UpdateManager} of the changes using * {@link UpdateManager#signalChunkLoad(Instance, int, int)} and {@link UpdateManager#signalChunkUnload(Instance, int, int)}. */ -public abstract class Instance implements BlockModifier, EventHandler, DataContainer, ForwardingAudience { +public abstract class Instance implements BlockModifier, EventHandler, DataContainer, PacketGroupingAudience { protected static final BlockManager BLOCK_MANAGER = MinecraftServer.getBlockManager(); protected static final UpdateManager UPDATE_MANAGER = MinecraftServer.getUpdateManager(); @@ -455,6 +456,7 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta * * @return an unmodifiable {@link Set} containing all the players in the instance */ + @Override @NotNull public Set getPlayers() { return Collections.unmodifiableSet(players); @@ -1105,9 +1107,4 @@ public abstract class Instance implements BlockModifier, EventHandler, DataConta public PFInstanceSpace getInstanceSpace() { return instanceSpace; } - - @Override - public @NotNull Iterable audiences() { - return this.getPlayers(); - } } \ No newline at end of file diff --git a/src/main/java/net/minestom/server/network/ConnectionManager.java b/src/main/java/net/minestom/server/network/ConnectionManager.java index 1201b885a..2f5de87f8 100644 --- a/src/main/java/net/minestom/server/network/ConnectionManager.java +++ b/src/main/java/net/minestom/server/network/ConnectionManager.java @@ -6,6 +6,8 @@ import net.kyori.adventure.audience.ForwardingAudience; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; +import net.minestom.server.adventure.audience.Audiences; +import net.minestom.server.adventure.audience.PacketGroupingAudience; import net.minestom.server.chat.JsonMessage; import net.minestom.server.entity.Player; import net.minestom.server.entity.fakeplayer.FakePlayer; @@ -33,11 +35,12 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Consumer; +import java.util.function.Predicate; /** * Manages the connected clients. */ -public final class ConnectionManager implements ForwardingAudience { +public final class ConnectionManager implements PacketGroupingAudience { private static final long KEEP_ALIVE_DELAY = 10_000; private static final long KEEP_ALIVE_KICK = 30_000; @@ -145,7 +148,7 @@ public final class ConnectionManager implements ForwardingAudience { * @param jsonMessage the message to send, probably a {@link net.minestom.server.chat.ColoredText} or {@link net.minestom.server.chat.RichMessage} * @param condition the condition to receive the message * - * @deprecated Use {@link Audience#sendMessage(Component)} on {@link #audiences(PlayerValidator)} + * @deprecated Use {@link Audiences#players(Predicate)} */ @Deprecated public void broadcastMessage(@NotNull JsonMessage jsonMessage, @Nullable PlayerValidator condition) { @@ -554,30 +557,7 @@ public final class ConnectionManager implements ForwardingAudience { } @Override - public @NotNull Iterable audiences() { + public @NotNull Collection getPlayers() { return this.getOnlinePlayers(); } - - /** - * Gets the audiences of players who match a given validator. - * - * @param validator the validator - * - * @return the audience - */ - public @NotNull Iterable audiences(@Nullable PlayerValidator validator) { - if (validator == null) { - return this.audiences(); - } - - List validatedPlayers = new ArrayList<>(); - - for (Player onlinePlayer : this.getOnlinePlayers()) { - if (validator.isValid(onlinePlayer)) { - validatedPlayers.add(onlinePlayer); - } - } - - return validatedPlayers; - } } diff --git a/src/main/java/net/minestom/server/scoreboard/Scoreboard.java b/src/main/java/net/minestom/server/scoreboard/Scoreboard.java index e16825a72..01831e231 100644 --- a/src/main/java/net/minestom/server/scoreboard/Scoreboard.java +++ b/src/main/java/net/minestom/server/scoreboard/Scoreboard.java @@ -4,16 +4,19 @@ import net.kyori.adventure.audience.Audience; import net.kyori.adventure.audience.ForwardingAudience; import net.kyori.adventure.text.Component; import net.minestom.server.Viewable; +import net.minestom.server.adventure.audience.PacketGroupingAudience; import net.minestom.server.entity.Player; import net.minestom.server.network.packet.server.play.DisplayScoreboardPacket; import net.minestom.server.network.packet.server.play.ScoreboardObjectivePacket; import net.minestom.server.network.packet.server.play.UpdateScorePacket; import org.jetbrains.annotations.NotNull; +import java.util.Collection; + /** * This interface represents all scoreboard of Minecraft. */ -public interface Scoreboard extends Viewable, ForwardingAudience { +public interface Scoreboard extends Viewable, PacketGroupingAudience { /** * Creates a creation objective packet. @@ -101,7 +104,7 @@ public interface Scoreboard extends Viewable, ForwardingAudience { String getObjectiveName(); @Override - @NotNull default Iterable audiences() { + @NotNull default Collection getPlayers() { return this.getViewers(); } } diff --git a/src/main/java/net/minestom/server/scoreboard/Team.java b/src/main/java/net/minestom/server/scoreboard/Team.java index 004095f8e..e851aad54 100644 --- a/src/main/java/net/minestom/server/scoreboard/Team.java +++ b/src/main/java/net/minestom/server/scoreboard/Team.java @@ -7,6 +7,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; import net.minestom.server.adventure.AdventurePacketConvertor; +import net.minestom.server.adventure.audience.PacketGroupingAudience; import net.minestom.server.chat.ChatColor; import net.minestom.server.chat.JsonMessage; import net.minestom.server.entity.LivingEntity; @@ -18,6 +19,7 @@ import net.minestom.server.network.packet.server.play.TeamsPacket.NameTagVisibil import net.minestom.server.utils.PacketUtils; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.Collections; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -25,7 +27,7 @@ import java.util.concurrent.CopyOnWriteArraySet; /** * This object represents a team on a scoreboard that has a common display theme and other properties. */ -public class Team implements ForwardingAudience { +public class Team implements PacketGroupingAudience { private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager(); @@ -570,7 +572,7 @@ public class Team implements ForwardingAudience { } @Override - public @NotNull Iterable audiences() { + public @NotNull Collection getPlayers() { if (!this.isPlayerMembersUpToDate) { this.playerMembers.clear(); diff --git a/src/main/java/net/minestom/server/utils/callback/validator/Validator.java b/src/main/java/net/minestom/server/utils/callback/validator/Validator.java index 081f77971..1393ebcd6 100644 --- a/src/main/java/net/minestom/server/utils/callback/validator/Validator.java +++ b/src/main/java/net/minestom/server/utils/callback/validator/Validator.java @@ -2,11 +2,13 @@ package net.minestom.server.utils.callback.validator; import org.jetbrains.annotations.NotNull; +import java.util.function.Predicate; + /** * Interface used when a value needs to be validated dynamically. */ @FunctionalInterface -public interface Validator { +public interface Validator extends Predicate { /** * Gets if a value is valid based on a condition. @@ -16,4 +18,12 @@ public interface Validator { */ boolean isValid(@NotNull T value); + @Override + default boolean test(T t) { + if (t == null) { + return false; + } else { + return this.isValid(t); + } + } }