From de5d8a76fee2c8b0f5c98fece077b17397d5680c Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Fri, 21 May 2021 15:55:54 -0700 Subject: [PATCH] Enhance (Async)ChatEvent with per-viewer rendering API (#5684) --- Spigot-API-Patches/Adventure.patch | 269 +++++++++++++++++- Spigot-Server-Patches/Adventure.patch | 115 ++++++-- ...nilla-per-world-scoreboard-coloring-.patch | 2 +- 3 files changed, 346 insertions(+), 40 deletions(-) diff --git a/Spigot-API-Patches/Adventure.patch b/Spigot-API-Patches/Adventure.patch index f9a91368ac..5c4dc1b51b 100644 --- a/Spigot-API-Patches/Adventure.patch +++ b/Spigot-API-Patches/Adventure.patch @@ -116,7 +116,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +/** + * A chat composer is responsible for composing chat messages sent by {@link Player}s to the server. ++ * ++ * @deprecated for removal with 1.17, in favor of {@link ChatRenderer} + */ ++@Deprecated +@FunctionalInterface +public interface ChatComposer { + ChatComposer DEFAULT = (player, displayName, message) -> Component.translatable("chat.type.text", displayName, message); @@ -128,7 +131,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * @param displayName the display name of the {@link Player} sending the message + * @param message the chat message + * @return a composed chat message ++ * @deprecated for removal with 1.17 + */ ++ @Deprecated + @NotNull + Component composeChat(final @NotNull Player source, final @NotNull Component displayName, final @NotNull Component message); +} @@ -147,7 +152,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +/** + * A chat formatter is responsible for the formatting of chat messages sent by {@link Player}s to the server. + * -+ * @deprecated in favour of {@link ChatComposer} ++ * @deprecated for removal with 1.17, in favour of {@link ChatRenderer} + */ +@Deprecated +@FunctionalInterface @@ -161,11 +166,85 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * @param displayName the display name of the {@link Player} sending the message + * @param message the chat message + * @return a formatted chat message ++ * @deprecated for removal with 1.17 + */ + @Deprecated + @NotNull + Component chat(final @NotNull Component displayName, final @NotNull Component message); +} +diff --git a/src/main/java/io/papermc/paper/chat/ChatRenderer.java b/src/main/java/io/papermc/paper/chat/ChatRenderer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/chat/ChatRenderer.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.chat; ++ ++import net.kyori.adventure.audience.Audience; ++import net.kyori.adventure.text.Component; ++import org.bukkit.entity.Player; ++import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * A chat renderer is responsible for rendering chat messages sent by {@link Player}s to the server. ++ */ ++@FunctionalInterface ++public interface ChatRenderer { ++ ChatRenderer DEFAULT = viewerUnaware((source, sourceDisplayName, message) -> Component.translatable("chat.type.text", sourceDisplayName, message)); ++ ++ /** ++ * Renders a chat message. This will be called once for each receiving {@link Audience}. ++ * ++ * @param source the message source ++ * @param sourceDisplayName the display name of the source player ++ * @param message the chat message ++ * @param viewer the receiving {@link Audience} ++ * @return a rendered chat message ++ */ ++ @NotNull ++ Component render(@NotNull Player source, @NotNull Component sourceDisplayName, @NotNull Component message, @NotNull Audience viewer); ++ ++ /** ++ * Creates a new viewer-unaware {@link ChatRenderer}, which will render the chat message a single time, ++ * displaying the same rendered message to every viewing {@link Audience}. ++ * ++ * @param renderer the viewer unaware renderer ++ * @return a new {@link ChatRenderer} ++ */ ++ @NotNull ++ static ChatRenderer viewerUnaware(final @NotNull ViewerUnaware renderer) { ++ return new ChatRenderer() { ++ private @MonotonicNonNull Component message; ++ ++ @Override ++ public @NotNull Component render(final @NotNull Player source, final @NotNull Component sourceDisplayName, final @NotNull Component message, final @NotNull Audience viewer) { ++ if (this.message == null) { ++ this.message = renderer.render(source, sourceDisplayName, message); ++ } ++ return this.message; ++ } ++ }; ++ } ++ ++ /** ++ * Similar to {@link ChatRenderer}, but without knowledge of the message viewer. ++ * ++ * @see ChatRenderer#viewerUnaware(ViewerUnaware) ++ */ ++ interface ViewerUnaware { ++ /** ++ * Renders a chat message. ++ * ++ * @param source the message source ++ * @param sourceDisplayName the display name of the source player ++ * @param message the chat message ++ * @return a rendered chat message ++ */ ++ @NotNull ++ Component render(@NotNull Player source, @NotNull Component sourceDisplayName, @NotNull Component message); ++ } ++} diff --git a/src/main/java/io/papermc/paper/event/player/AbstractChatEvent.java b/src/main/java/io/papermc/paper/event/player/AbstractChatEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -176,11 +255,17 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + +import io.papermc.paper.chat.ChatComposer; +import io.papermc.paper.chat.ChatFormatter; ++import java.util.HashSet; +import java.util.Set; ++import io.papermc.paper.chat.ChatRenderer; ++import net.kyori.adventure.audience.Audience; ++import net.kyori.adventure.audience.ForwardingAudience; +import net.kyori.adventure.text.Component; ++import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.player.PlayerEvent; ++import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.NotNull; + @@ -190,25 +275,83 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * An abstract implementation of a chat event, handling shared logic. + */ +public abstract class AbstractChatEvent extends PlayerEvent implements Cancellable { -+ private final Set<Player> recipients; ++ private final Set<Audience> viewers; ++ @Deprecated private final Set<Player> recipients; + private boolean cancelled = false; -+ private ChatComposer composer; ++ private ChatRenderer renderer; ++ @Deprecated private @Nullable ChatComposer composer; + @Deprecated private @Nullable ChatFormatter formatter; ++ private final Component originalMessage; + private Component message; + ++ AbstractChatEvent(final boolean async, final @NotNull Player player, final @NotNull Set<Audience> viewers, final @NotNull ChatRenderer renderer, final @NotNull Component message) { ++ super(player, async); ++ this.viewers = viewers; ++ this.recipients = new HashSet<>(Bukkit.getOnlinePlayers()); ++ this.renderer = renderer; ++ this.message = message; ++ this.originalMessage = message; ++ } ++ ++ /** ++ * @deprecated for removal with 1.17 ++ */ ++ @Deprecated ++ AbstractChatEvent(final boolean async, final @NotNull Player player, final @NotNull Set<Player> recipients, final @NotNull Set<Audience> viewers, final @NotNull ChatRenderer renderer, final @NotNull Component message) { ++ super(player, async); ++ this.recipients = recipients; ++ this.viewers = viewers; ++ this.renderer = renderer; ++ this.message = message; ++ this.originalMessage = message; ++ } ++ ++ /** ++ * @deprecated for removal with 1.17 ++ */ ++ @Deprecated + AbstractChatEvent(final boolean async, final @NotNull Player player, final @NotNull Set<Player> recipients, final @NotNull ChatComposer composer, final @NotNull Component message) { + super(player, async); + this.recipients = recipients; ++ final Set<Audience> audiences = new HashSet<>(recipients); ++ audiences.add(Bukkit.getConsoleSender()); ++ this.viewers = audiences; + this.composer = composer; + this.message = message; ++ this.originalMessage = message; + } + ++ /** ++ * @deprecated for removal with 1.17 ++ */ + @Deprecated + AbstractChatEvent(final boolean async, final @NotNull Player player, final @NotNull Set<Player> recipients, final @NotNull ChatFormatter formatter, final @NotNull Component message) { + super(player, async); + this.recipients = recipients; ++ final Set<Audience> audiences = new HashSet<>(recipients); ++ audiences.add(Bukkit.getConsoleSender()); ++ this.viewers = audiences; + this.formatter = formatter; + this.message = message; ++ this.originalMessage = message; ++ } ++ ++ /** ++ * Gets a set of {@link Audience audiences} that this chat message will be displayed to. ++ * ++ * <p>The set returned is not guaranteed to be mutable and may auto-populate ++ * on access. Any listener accessing the returned set should be aware that ++ * it may reduce performance for a lazy set implementation.</p> ++ * ++ * <p>Listeners should be aware that modifying the list may throw {@link ++ * UnsupportedOperationException} if the event caller provides an ++ * unmodifiable set.</p> ++ * ++ * @return a set of {@link Audience audiences} who will receive the chat message ++ */ ++ @NotNull ++ public final Set<Audience> viewers() { ++ return this.viewers; + } + + /** @@ -223,22 +366,60 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * unmodifiable set.</p> + * + * @return a set of players who will receive the chat message ++ * @deprecated for removal with 1.17, in favor of {@link #viewers()} + */ ++ @Deprecated + @NotNull + public final Set<Player> recipients() { + return this.recipients; + } + + /** ++ * Sets the chat renderer. ++ * ++ * @param renderer the chat renderer ++ * @throws NullPointerException if {@code renderer} is {@code null} ++ */ ++ public final void renderer(final @NotNull ChatRenderer renderer) { ++ this.renderer = requireNonNull(renderer, "renderer"); ++ this.formatter = null; ++ this.composer = null; ++ } ++ ++ /** ++ * Gets the chat renderer. ++ * ++ * @return the chat renderer ++ */ ++ @NotNull ++ public final ChatRenderer renderer() { ++ if(this.renderer == null) { ++ if(this.composer != null) { ++ this.renderer = ChatRenderer.viewerUnaware((source, displayName, message) -> this.composer.composeChat(source, source.displayName(), message)); ++ } else { ++ requireNonNull(this.formatter, "renderer, composer, and formatter"); ++ this.renderer = ChatRenderer.viewerUnaware((source, displayName, message) -> this.formatter.chat(source.displayName(), message)); ++ } ++ } ++ return this.renderer; ++ } ++ ++ /** + * Gets the chat composer. + * + * @return the chat composer ++ * @deprecated for removal with 1.17, in favour of {@link #renderer()} + */ ++ @Deprecated + @NotNull + public final ChatComposer composer() { + if(this.composer == null) { -+ requireNonNull(this.formatter, "composer and formatter"); -+ this.composer = (source, displayName, message) -> this.formatter.chat(displayName, message); ++ if(this.renderer != null) { ++ this.composer = (source, displayName, message) -> this.renderer.render(source, displayName, message, this.legacyForwardingAudience()); ++ } else { ++ requireNonNull(this.formatter, "renderer, composer, and formatter"); ++ this.composer = (source, displayName, message) -> this.formatter.chat(displayName, message); ++ } + } + return this.composer; + } @@ -248,23 +429,31 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * + * @param composer the chat composer + * @throws NullPointerException if {@code composer} is {@code null} ++ * @deprecated for removal with 1.17, in favour of {@link #renderer(ChatRenderer)} + */ ++ @Deprecated + public final void composer(final @NotNull ChatComposer composer) { + this.composer = requireNonNull(composer, "composer"); + this.formatter = null; ++ this.renderer = null; + } + + /** + * Gets the chat formatter. + * + * @return the chat formatter -+ * @deprecated in favour of {@link #composer()} ++ * @deprecated for removal with 1.17, in favour of {@link #renderer()} + */ + @Deprecated + @NotNull + public final ChatFormatter formatter() { + if(this.formatter == null) { -+ this.formatter = (displayName, message) -> this.composer.composeChat(this.player, displayName, message); ++ if(this.renderer != null) { ++ this.formatter = (displayName, message) -> this.renderer.render(this.player, displayName, message, this.legacyForwardingAudience()); ++ } else { ++ requireNonNull(this.composer, "renderer, composer, and formatter"); ++ this.formatter = (displayName, message) -> this.composer.composeChat(this.player, displayName, message); ++ } + } + return this.formatter; + } @@ -274,16 +463,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + * + * @param formatter the chat formatter + * @throws NullPointerException if {@code formatter} is {@code null} -+ * @deprecated in favour of {@link #composer(ChatComposer)} ++ * @deprecated for removal with 1.17, in favour of {@link #renderer(ChatRenderer)} + */ + @Deprecated + public final void formatter(final @NotNull ChatFormatter formatter) { + this.formatter = requireNonNull(formatter, "formatter"); -+ this.composer = (source, displayName, message) -> formatter.chat(displayName, message); ++ this.composer = null; ++ this.renderer = null; + } + + /** + * Gets the user-supplied message. ++ * The return value will reflect changes made using {@link #message(Component)}. + * + * @return the user-supplied message + */ @@ -302,6 +493,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.message = requireNonNull(message, "message"); + } + ++ /** ++ * Gets the original and unmodified user-supplied message. ++ * The return value will <b>not</b> reflect changes made using ++ * {@link #message(Component)}. ++ * ++ * @return the original user-supplied message ++ */ ++ @NotNull ++ public final Component originalMessage() { ++ return this.originalMessage; ++ } ++ + @Override + public final boolean isCancelled() { + return this.cancelled; @@ -311,6 +514,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public final void setCancelled(final boolean cancelled) { + this.cancelled = cancelled; + } ++ ++ private @NotNull Audience legacyForwardingAudience() { ++ return new ForwardingAudience() { ++ @Override ++ public @NonNull Iterable<? extends Audience> audiences() { ++ return AbstractChatEvent.this.viewers; ++ } ++ }; ++ } +} diff --git a/src/main/java/io/papermc/paper/event/player/AsyncChatEvent.java b/src/main/java/io/papermc/paper/event/player/AsyncChatEvent.java new file mode 100644 @@ -323,6 +535,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import io.papermc.paper.chat.ChatComposer; +import io.papermc.paper.chat.ChatFormatter; +import java.util.Set; ++import io.papermc.paper.chat.ChatRenderer; ++import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; @@ -334,12 +548,28 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +public final class AsyncChatEvent extends AbstractChatEvent { + private static final HandlerList HANDLERS = new HandlerList(); + ++ public AsyncChatEvent(final boolean async, final @NotNull Player player, final @NotNull Set<Audience> viewers, final @NotNull ChatRenderer renderer, final @NotNull Component message) { ++ super(async, player, viewers, renderer, message); ++ } ++ ++ /** ++ * @deprecated for removal with 1.17, use {@link #AsyncChatEvent(boolean, Player, Set, ChatRenderer, Component)} ++ */ ++ @Deprecated ++ public AsyncChatEvent(final boolean async, final @NotNull Player player, final @NotNull Set<Player> recipients, final @NotNull Set<Audience> viewers, final @NotNull ChatRenderer renderer, final @NotNull Component message) { ++ super(async, player, recipients, viewers, renderer, message); ++ } ++ ++ /** ++ * @deprecated for removal with 1.17, use {@link #AsyncChatEvent(boolean, Player, Set, ChatRenderer, Component)} ++ */ ++ @Deprecated + public AsyncChatEvent(final boolean async, final @NotNull Player player, final @NotNull Set<Player> recipients, final @NotNull ChatComposer composer, final @NotNull Component message) { + super(async, player, recipients, composer, message); + } + + /** -+ * @deprecated use {@link #AsyncChatEvent(boolean, Player, Set, ChatComposer, Component)} ++ * @deprecated for removal with 1.17, use {@link #AsyncChatEvent(boolean, Player, Set, ChatRenderer, Component)} + */ + @Deprecated + public AsyncChatEvent(final boolean async, final @NotNull Player player, final @NotNull Set<Player> recipients, final @NotNull ChatFormatter formatter, final @NotNull Component message) { @@ -368,6 +598,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import io.papermc.paper.chat.ChatComposer; +import io.papermc.paper.chat.ChatFormatter; +import java.util.Set; ++import io.papermc.paper.chat.ChatRenderer; ++import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import org.bukkit.Warning; +import org.bukkit.entity.Player; @@ -384,12 +616,27 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +public final class ChatEvent extends AbstractChatEvent { + private static final HandlerList HANDLERS = new HandlerList(); + ++ public ChatEvent(final @NotNull Player player, final @NotNull Set<Audience> viewers, final @NotNull ChatRenderer renderer, final @NotNull Component message) { ++ super(false, player, viewers, renderer, message); ++ } ++ ++ /** ++ * @deprecated for removal with 1.17, use {@link #ChatEvent(Player, Set, ChatRenderer, Component)} ++ */ ++ public ChatEvent(final @NotNull Player player, final @NotNull Set<Player> recipients, final @NotNull Set<Audience> viewers, final @NotNull ChatRenderer renderer, final @NotNull Component message) { ++ super(false, player, recipients, viewers, renderer, message); ++ } ++ ++ /** ++ * @deprecated for removal with 1.17, use {@link #ChatEvent(Player, Set, ChatRenderer, Component)} ++ */ ++ @Deprecated + public ChatEvent(final @NotNull Player player, final @NotNull Set<Player> recipients, final @NotNull ChatComposer composer, final @NotNull Component message) { + super(false, player, recipients, composer, message); + } + + /** -+ * @deprecated use {@link #ChatEvent(Player, Set, ChatComposer, Component)} ++ * @deprecated for removal with 1.17, use {@link #ChatEvent(Player, Set, ChatRenderer, Component)} + */ + @Deprecated + public ChatEvent(final @NotNull Player player, final @NotNull Set<Player> recipients, final @NotNull ChatFormatter formatter, final @NotNull Component message) { diff --git a/Spigot-Server-Patches/Adventure.patch b/Spigot-Server-Patches/Adventure.patch index f2f279c1e1..72ab1a1017 100644 --- a/Spigot-Server-Patches/Adventure.patch +++ b/Spigot-Server-Patches/Adventure.patch @@ -111,7 +111,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +package io.papermc.paper.adventure; + -+import io.papermc.paper.chat.ChatComposer; ++import io.papermc.paper.chat.ChatRenderer; +import io.papermc.paper.event.player.AbstractChatEvent; +import io.papermc.paper.event.player.AsyncChatEvent; +import io.papermc.paper.event.player.ChatEvent; @@ -120,11 +120,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import java.util.function.Consumer; +import java.util.regex.Pattern; + ++import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.audience.MessageType; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextReplacementConfig; +import net.kyori.adventure.text.event.ClickEvent; -+import net.minecraft.network.chat.ChatMessageType; +import net.minecraft.network.chat.IChatBaseComponent; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.EntityPlayer; @@ -171,7 +171,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // continuing from AsyncPlayerChatEvent (without PlayerChatEvent) + event -> { + this.processModern( -+ legacyComposer(event.getFormat()), ++ legacyRenderer(event.getFormat()), + event.getRecipients(), + PaperAdventure.LEGACY_SECTION_UXRC.deserialize(event.getMessage()), + event.isCancelled() @@ -180,7 +180,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // continuing from AsyncPlayerChatEvent and PlayerChatEvent + event -> { + this.processModern( -+ legacyComposer(event.getFormat()), ++ legacyRenderer(event.getFormat()), + event.getRecipients(), + PaperAdventure.LEGACY_SECTION_UXRC.deserialize(event.getMessage()), + event.isCancelled() @@ -189,7 +189,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // no legacy events called, all nice and fresh! + () -> { + this.processModern( -+ ChatComposer.DEFAULT, ++ ChatRenderer.DEFAULT, + new LazyPlayerSet(this.server), + Component.text(this.message).replaceText(URL_REPLACEMENT_CONFIG), + false @@ -229,8 +229,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + -+ private void processModern(final ChatComposer composer, final Set<Player> recipients, final Component message, final boolean cancelled) { -+ final AsyncChatEvent ae = this.createAsync(composer, recipients, message); ++ private void processModern(final ChatRenderer renderer, final Set<Player> recipients, final Component message, final boolean cancelled) { ++ final AsyncChatEvent ae = this.createAsync(renderer, recipients, new LazyChatAudienceSet(), message); + ae.setCancelled(cancelled); // propagate cancelled state + post(ae); + final boolean listenersOnSyncEvent = anyListeners(ChatEvent.getHandlerList()); @@ -245,7 +245,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.queueIfAsyncOrRunImmediately(new Waitable<Void>() { + @Override + protected Void evaluate() { -+ final ChatEvent se = ChatProcessor.this.createSync(ae.composer(), ae.recipients(), ae.message()); ++ final ChatEvent se = ChatProcessor.this.createSync(ae.renderer(), ae.recipients(), ae.viewers(), ae.message()); + se.setCancelled(ae.isCancelled()); // propagate cancelled state + post(se); + ChatProcessor.this.complete(se); @@ -260,33 +260,31 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + final CraftPlayer player = this.player.getBukkitEntity(); ++ final Component displayName = displayName(player); ++ final Component message = event.message(); ++ final ChatRenderer renderer = event.renderer(); + -+ final Component message = event.composer().composeChat( -+ event.getPlayer(), -+ displayName(player), -+ event.message() -+ ); -+ -+ this.server.console.sendMessage(message); -+ -+ if (((LazyPlayerSet) event.recipients()).isLazy()) { -+ final IChatBaseComponent vanilla = PaperAdventure.asVanilla(message); -+ for (final EntityPlayer recipient : this.server.getPlayerList().players) { -+ recipient.sendMessage(vanilla, ChatMessageType.CHAT, this.player.getUniqueID()); ++ final Set<Audience> viewers = event.viewers(); ++ final Set<Player> recipients = event.recipients(); ++ if (viewers instanceof LazyChatAudienceSet && recipients instanceof LazyPlayerSet && ++ (!((LazyChatAudienceSet) viewers).isLazy() || ((LazyPlayerSet) recipients).isLazy())) { ++ for (final Audience viewer : viewers) { ++ viewer.sendMessage(player, renderer.render(player, displayName, message, viewer), MessageType.CHAT); + } + } else { -+ for (final Player recipient : event.recipients()) { -+ recipient.sendMessage(player, message, MessageType.CHAT); ++ this.server.console.sendMessage(player, renderer.render(player, displayName, message, this.server.console), MessageType.CHAT); ++ for (final Player recipient : recipients) { ++ recipient.sendMessage(player, renderer.render(player, displayName, message, recipient), MessageType.CHAT); + } + } + } + -+ private AsyncChatEvent createAsync(final ChatComposer composer, final Set<Player> recipients, final Component message) { -+ return new AsyncChatEvent(this.async, this.player.getBukkitEntity(), recipients, composer, message); ++ private AsyncChatEvent createAsync(final ChatRenderer renderer, final Set<Player> recipients, final Set<Audience> viewers, final Component message) { ++ return new AsyncChatEvent(this.async, this.player.getBukkitEntity(), recipients, viewers, renderer, message); + } + -+ private ChatEvent createSync(final ChatComposer composer, final Set<Player> recipients, final Component message) { -+ return new ChatEvent(this.player.getBukkitEntity(), recipients, composer, message); ++ private ChatEvent createSync(final ChatRenderer renderer, final Set<Player> recipients, final Set<Audience> viewers, final Component message) { ++ return new ChatEvent(this.player.getBukkitEntity(), recipients, viewers, renderer, message); + } + + private static String legacyDisplayName(final CraftPlayer player) { @@ -297,8 +295,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return player.displayName(); + } + -+ private static ChatComposer legacyComposer(final String format) { -+ return (player, displayName, message) -> PaperAdventure.LEGACY_SECTION_UXRC.deserialize(String.format(format, legacyDisplayName((CraftPlayer) player), PaperAdventure.LEGACY_SECTION_UXRC.serialize(message))).replaceText(URL_REPLACEMENT_CONFIG); ++ private static ChatRenderer legacyRenderer(final String format) { ++ return (player, displayName, message, recipient) -> PaperAdventure.LEGACY_SECTION_UXRC.deserialize(String.format(format, legacyDisplayName((CraftPlayer) player), PaperAdventure.LEGACY_SECTION_UXRC.serialize(message))).replaceText(URL_REPLACEMENT_CONFIG); + } + + private void queueIfAsyncOrRunImmediately(final Waitable<Void> waitable) { @@ -352,6 +350,33 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return PaperAdventure.LEGACY_SECTION_UXRC.serialize(player.adventure$displayName); + } +} +diff --git a/src/main/java/io/papermc/paper/adventure/LazyChatAudienceSet.java b/src/main/java/io/papermc/paper/adventure/LazyChatAudienceSet.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/LazyChatAudienceSet.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.adventure; ++ ++import net.kyori.adventure.audience.Audience; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.Bukkit; ++import org.bukkit.craftbukkit.util.LazyHashSet; ++import org.bukkit.craftbukkit.util.LazyPlayerSet; ++import org.bukkit.entity.Player; ++ ++import java.util.HashSet; ++import java.util.Set; ++ ++final class LazyChatAudienceSet extends LazyHashSet<Audience> { ++ @Override ++ protected Set<Audience> makeReference() { ++ final Set<Player> playerSet = LazyPlayerSet.makePlayerSet(MinecraftServer.getServer()); ++ final HashSet<Audience> audiences = new HashSet<>(playerSet); ++ audiences.add(Bukkit.getConsoleSender()); ++ return audiences; ++ } ++} diff --git a/src/main/java/io/papermc/paper/adventure/NBTLegacyHoverEventSerializer.java b/src/main/java/io/papermc/paper/adventure/NBTLegacyHoverEventSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -3235,3 +3260,37 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 public static IBlockData getBlock(MaterialData material) { return getBlock(material.getItemType(), material.getData()); } +diff --git a/src/main/java/org/bukkit/craftbukkit/util/LazyHashSet.java b/src/main/java/org/bukkit/craftbukkit/util/LazyHashSet.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/LazyHashSet.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/LazyHashSet.java +@@ -0,0 +0,0 @@ public abstract class LazyHashSet<E> implements Set<E> { + return this.reference = makeReference(); + } + +- abstract Set<E> makeReference(); ++ protected abstract Set<E> makeReference(); // Paper - protected + + public boolean isLazy() { + return reference == null; +diff --git a/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java b/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/LazyPlayerSet.java +@@ -0,0 +0,0 @@ public class LazyPlayerSet extends LazyHashSet<Player> { + } + + @Override +- HashSet<Player> makeReference() { ++ protected HashSet<Player> makeReference() { // Paper - protected + if (reference != null) { + throw new IllegalStateException("Reference already created!"); + } ++ // Paper start ++ return makePlayerSet(this.server); ++ } ++ public static HashSet<Player> makePlayerSet(final MinecraftServer server) { ++ // Paper end + List<EntityPlayer> players = server.getPlayerList().players; + HashSet<Player> reference = new HashSet<Player>(players.size()); + for (EntityPlayer player : players) { diff --git a/Spigot-Server-Patches/Option-to-use-vanilla-per-world-scoreboard-coloring-.patch b/Spigot-Server-Patches/Option-to-use-vanilla-per-world-scoreboard-coloring-.patch index 1f1dfcd5e3..f32e2e0813 100644 --- a/Spigot-Server-Patches/Option-to-use-vanilla-per-world-scoreboard-coloring-.patch +++ b/Spigot-Server-Patches/Option-to-use-vanilla-per-world-scoreboard-coloring-.patch @@ -29,7 +29,7 @@ diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/m index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java -@@ -0,0 +0,0 @@ import net.minecraft.network.chat.ChatMessageType; +@@ -0,0 +0,0 @@ import net.kyori.adventure.text.event.ClickEvent; import net.minecraft.network.chat.IChatBaseComponent; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.EntityPlayer;