diff --git a/patches/api/Adventure.patch b/patches/api/Adventure.patch index b17b7ff027..fa47823c6a 100644 --- a/patches/api/Adventure.patch +++ b/patches/api/Adventure.patch @@ -359,8 +359,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private static final HandlerList HANDLER_LIST = new HandlerList(); + + @ApiStatus.Internal -+ public AsyncChatCommandDecorateEvent(boolean async, @Nullable Player player, @NotNull Component originalMessage, @NotNull Component result) { -+ super(async, player, originalMessage, result); ++ public AsyncChatCommandDecorateEvent(@Nullable Player player, @NotNull Component originalMessage) { ++ super(player, originalMessage); + } + + @Override @@ -384,7 +384,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; -+import org.bukkit.event.player.AsyncPlayerChatPreviewEvent; +import org.bukkit.event.server.ServerEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; @@ -412,11 +411,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private boolean cancelled; + + @ApiStatus.Internal -+ public AsyncChatDecorateEvent(final boolean async, final @Nullable Player player, final @NotNull Component originalMessage, final @NotNull Component result) { -+ super(async); ++ public AsyncChatDecorateEvent(final @Nullable Player player, final @NotNull Component originalMessage) { ++ super(true); + this.player = player; + this.originalMessage = originalMessage; -+ this.result = result; ++ this.result = originalMessage; + } + + /** @@ -443,7 +442,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + /** + * Gets the decoration result. This may already be different from + * {@link #originalMessage()} if some other listener to this event -+ * OR the legacy preview event ({@link AsyncPlayerChatPreviewEvent}) + * changed the result. + * + * @return the result diff --git a/patches/server/Adventure.patch b/patches/server/Adventure.patch index 47707978b1..bab4ef08f7 100644 --- a/patches/server/Adventure.patch +++ b/patches/server/Adventure.patch @@ -416,7 +416,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + }); + } + -+ static @Nullable String tryCollapseToString(final Component component) { ++ public static @Nullable String tryCollapseToString(final Component component) { + if (component instanceof final TextComponent textComponent) { + if (component.children().isEmpty() && component.style().isEmpty()) { + return textComponent.content(); @@ -611,157 +611,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } +} -diff --git a/src/main/java/io/papermc/paper/adventure/ChatDecorationProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatDecorationProcessor.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/adventure/ChatDecorationProcessor.java -@@ -0,0 +0,0 @@ -+package io.papermc.paper.adventure; -+ -+import io.papermc.paper.event.player.AsyncChatCommandDecorateEvent; -+import io.papermc.paper.event.player.AsyncChatDecorateEvent; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.concurrent.CompletableFuture; -+import java.util.regex.Pattern; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.minimessage.MiniMessage; -+import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; -+import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; -+import net.minecraft.Optionull; -+import net.minecraft.commands.CommandSourceStack; -+import net.minecraft.network.chat.ChatDecorator; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerPlayer; -+import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.craftbukkit.util.LazyPlayerSet; -+import org.bukkit.event.Event; -+import org.bukkit.event.player.AsyncPlayerChatPreviewEvent; -+import org.checkerframework.checker.nullness.qual.NonNull; -+import org.checkerframework.checker.nullness.qual.Nullable; -+import org.checkerframework.framework.qual.DefaultQualifier; -+ -+import static io.papermc.paper.adventure.ChatProcessor.DEFAULT_LEGACY_FORMAT; -+import static io.papermc.paper.adventure.ChatProcessor.canYouHearMe; -+import static io.papermc.paper.adventure.ChatProcessor.displayName; -+import static net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection; -+ -+@DefaultQualifier(NonNull.class) -+public final class ChatDecorationProcessor { -+ -+ private static final String DISPLAY_NAME_TAG = "---paper_dn---"; -+ private static final Pattern DISPLAY_NAME_PATTERN = Pattern.compile("%(1\\$)?s"); -+ private static final String CONTENT_TAG = "---paper_content---"; -+ private static final Pattern CONTENT_PATTERN = Pattern.compile("%(2\\$)?s"); -+ -+ final MinecraftServer server; -+ final @Nullable ServerPlayer player; -+ final @Nullable CommandSourceStack commandSourceStack; -+ final Component originalMessage; -+ -+ public ChatDecorationProcessor(final MinecraftServer server, final @Nullable ServerPlayer player, final @Nullable CommandSourceStack commandSourceStack, final net.minecraft.network.chat.Component originalMessage) { -+ this.server = server; -+ this.player = player; -+ this.commandSourceStack = commandSourceStack; -+ this.originalMessage = PaperAdventure.asAdventure(originalMessage); -+ } -+ -+ public CompletableFuture process() { -+ return CompletableFuture.supplyAsync(() -> { -+ ChatDecorator.Result result = new ChatDecorator.ModernResult(this.originalMessage, true, false); -+ if (listenToLegacy()) { -+ result = this.processLegacy(result); -+ } -+ return this.processModern(result); -+ }, this.server.chatExecutor); -+ } -+ -+ @SuppressWarnings("deprecation") -+ private static boolean listenToLegacy() { -+ return canYouHearMe(AsyncPlayerChatPreviewEvent.getHandlerList()); -+ } -+ -+ @SuppressWarnings("deprecation") -+ private ChatDecorator.Result processLegacy(final ChatDecorator.Result input) { -+ if (this.player != null) { -+ final CraftPlayer player = this.player.getBukkitEntity(); -+ final String originalMessage = legacySection().serialize(this.originalMessage); -+ final AsyncPlayerChatPreviewEvent event = new AsyncPlayerChatPreviewEvent(true, player, originalMessage, new LazyPlayerSet(this.server)); -+ this.post(event); -+ -+ final boolean isDefaultFormat = DEFAULT_LEGACY_FORMAT.equals(event.getFormat()); -+ if (event.isCancelled() || (isDefaultFormat && originalMessage.equals(event.getMessage()))) { -+ return input; -+ } else { -+ final Component message = legacySection().deserialize(event.getMessage()); -+ final Component component = isDefaultFormat ? message : legacyFormat(event.getFormat(), ((CraftPlayer) event.getPlayer()), legacySection().deserialize(event.getMessage())); -+ return legacy(component, event.getFormat(), new ChatDecorator.MessagePair(message, event.getMessage()), isDefaultFormat); -+ } -+ } -+ return input; -+ } -+ -+ private ChatDecorator.Result processModern(final ChatDecorator.Result input) { -+ final @Nullable CraftPlayer player = Optionull.map(this.player, ServerPlayer::getBukkitEntity); -+ -+ final Component initialResult = input.message().component(); -+ final AsyncChatDecorateEvent event; -+ if (this.commandSourceStack != null) { -+ // TODO more command decorate context -+ event = new AsyncChatCommandDecorateEvent(true, player, this.originalMessage, initialResult); -+ } else { -+ event = new AsyncChatDecorateEvent(true, player, this.originalMessage, initialResult); -+ } -+ this.post(event); -+ if (!event.isCancelled() && !event.result().equals(initialResult)) { -+ if (input instanceof ChatDecorator.LegacyResult legacyResult) { -+ if (legacyResult.hasNoFormatting()) { -+ /* -+ The MessagePair in the decoration result may be different at this point. This is because the legacy -+ decoration system requires the same modifications be made to the message, so we can't have the initial -+ message value for the legacy chat events be changed by the modern decorate event. -+ */ -+ return noFormatting(event.result(), legacyResult.format(), legacyResult.message().legacyMessage()); -+ } else { -+ final Component formatted = legacyFormat(legacyResult.format(), player, event.result()); -+ return withFormatting(formatted, legacyResult.format(), event.result(), legacyResult.message().legacyMessage()); -+ } -+ } else { -+ return new ChatDecorator.ModernResult(event.result(), true, false); -+ } -+ } -+ return input; -+ } -+ -+ private void post(final Event event) { -+ this.server.server.getPluginManager().callEvent(event); -+ } -+ -+ private static Component legacyFormat(final String format, final @Nullable CraftPlayer player, final Component message) { -+ final List args = new ArrayList<>(player != null ? 2 : 1); -+ if (player != null) { -+ args.add(Placeholder.component(DISPLAY_NAME_TAG, displayName(player))); -+ } -+ args.add(Placeholder.component(CONTENT_TAG, message)); -+ String miniMsg = MiniMessage.miniMessage().serialize(legacySection().deserialize(format)); -+ miniMsg = DISPLAY_NAME_PATTERN.matcher(miniMsg).replaceFirst("<" + DISPLAY_NAME_TAG + ">"); -+ miniMsg = CONTENT_PATTERN.matcher(miniMsg).replaceFirst("<" + CONTENT_TAG + ">"); -+ return MiniMessage.miniMessage().deserialize(miniMsg, TagResolver.resolver(args)); -+ } -+ -+ public static ChatDecorator.LegacyResult legacy(final Component maybeFormatted, final String format, final ChatDecorator.MessagePair message, final boolean hasNoFormatting) { -+ return new ChatDecorator.LegacyResult(maybeFormatted, format, message, hasNoFormatting, false); -+ } -+ -+ public static ChatDecorator.LegacyResult noFormatting(final Component component, final String format, final String legacyMessage) { -+ return new ChatDecorator.LegacyResult(component, format, new ChatDecorator.MessagePair(component, legacyMessage), true, true); -+ } -+ -+ public static ChatDecorator.LegacyResult withFormatting(final Component formatted, final String format, final Component message, final String legacyMessage) { -+ return new ChatDecorator.LegacyResult(formatted, format, new ChatDecorator.MessagePair(message, legacyMessage), false, true); -+ } -+} diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 @@ -789,10 +638,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import net.kyori.adventure.audience.ForwardingAudience; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.minecraft.Optionull; +import net.minecraft.Util; +import net.minecraft.core.registries.Registries; -+import net.minecraft.network.chat.ChatDecorator; +import net.minecraft.network.chat.ChatType; +import net.minecraft.network.chat.OutgoingChatMessage; +import net.minecraft.network.chat.PlayerChatMessage; @@ -812,6 +661,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; ++import org.intellij.lang.annotations.Subst; + +import static net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection; + @@ -830,33 +680,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + static final int MESSAGE_CHANGED = 1; + static final int FORMAT_CHANGED = 2; + static final int SENDER_CHANGED = 3; // Not used -+ // static final int FORCE_PREVIEW_USE = 4; // TODO (future, maybe?) + private final BitSet flags = new BitSet(3); + + public ChatProcessor(final MinecraftServer server, final ServerPlayer player, final PlayerChatMessage message, final boolean async) { + this.server = server; + this.player = player; -+ /* -+ CraftBukkit's preview/decoration system relies on both the "decorate" and chat event making the same modifications. If -+ there is unsigned content in the legacyMessage, that is because the player sent the legacyMessage without it being -+ previewed (probably by sending it too quickly). We can just ignore that because the same changes will -+ happen in the chat event. -+ -+ If unsigned content is present, it will be the same as `this.legacyMessage.signedContent().previewResult().component()`. -+ */ + this.message = message; + this.async = async; -+ if (this.message.requireResult().modernized()) { -+ this.craftbukkit$originalMessage = this.message.requireResult().message().legacyMessage(); -+ } else { -+ this.craftbukkit$originalMessage = message.signedContent(); -+ } -+ /* -+ this.paper$originalMessage is the input to paper's chat events. This should be the decorated message component. -+ Even if the legacy preview event modified the format, and the client signed the formatted message, this should -+ still just be the message component. -+ */ -+ this.paper$originalMessage = this.message.requireResult().message().component(); ++ this.craftbukkit$originalMessage = message.unsignedContent() != null ? LegacyComponentSerializer.legacySection().serialize(PaperAdventure.asAdventure(message.unsignedContent())) : message.signedContent(); ++ this.paper$originalMessage = PaperAdventure.asAdventure(this.message.decoratedContent()); + this.outgoing = OutgoingChatMessage.create(this.message); + } + @@ -910,8 +742,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private ChatRenderer modernRenderer(final String format) { + if (this.flags.get(FORMAT_CHANGED)) { + return legacyRenderer(format); -+ } else if (this.message.requireResult() instanceof ChatDecorator.LegacyResult legacyResult) { -+ return legacyRenderer(legacyResult.format()); + } else { + return defaultRenderer(); + } @@ -920,25 +750,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private Component modernMessage(final String legacyMessage) { + if (this.flags.get(MESSAGE_CHANGED)) { + return legacySection().deserialize(legacyMessage); -+ } else if (this.message.unsignedContent() == null && this.message.requireResult() instanceof ChatDecorator.LegacyResult legacyResult) { -+ return legacyResult.message().component(); + } else { + return this.paper$originalMessage; + } + } + + private void readLegacyModifications(final String message, final String format, final Player playerSender) { -+ if (this.message.requireResult() instanceof ChatDecorator.LegacyResult result) { -+ if (this.message.unsignedContent() != null && !result.modernized()) { -+ this.flags.set(MESSAGE_CHANGED, !message.equals(result.message().legacyMessage())); -+ } else { -+ this.flags.set(MESSAGE_CHANGED, !message.equals(this.craftbukkit$originalMessage)); -+ } -+ this.flags.set(FORMAT_CHANGED, !format.equals(result.format())); -+ } else { -+ this.flags.set(MESSAGE_CHANGED, !message.equals(this.craftbukkit$originalMessage)); -+ this.flags.set(FORMAT_CHANGED, !format.equals(DEFAULT_LEGACY_FORMAT)); -+ } ++ this.flags.set(MESSAGE_CHANGED, !message.equals(this.craftbukkit$originalMessage)); ++ this.flags.set(FORMAT_CHANGED, !format.equals(DEFAULT_LEGACY_FORMAT)); + this.flags.set(SENDER_CHANGED, playerSender != this.player.getBukkitEntity()); + } + @@ -967,15 +786,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + private void readModernModifications(final AbstractChatEvent chatEvent, final ChatRenderer originalRenderer) { -+ if (this.message.unsignedContent() != null) { -+ this.flags.set(MESSAGE_CHANGED, !chatEvent.message().equals(this.message.requireResult().message().component())); -+ } else { -+ this.flags.set(MESSAGE_CHANGED, !chatEvent.message().equals(this.paper$originalMessage)); -+ } ++ this.flags.set(MESSAGE_CHANGED, !chatEvent.message().equals(this.paper$originalMessage)); + if (originalRenderer != chatEvent.renderer()) { // don't set to false if it hasn't changed + this.flags.set(FORMAT_CHANGED, true); + } -+ // this.flags.set(FORCE_PREVIEW_USE, chatEvent.usePreviewComponent()); // TODO (future, maybe?) + } + + private void complete(final AbstractChatEvent event) { @@ -993,9 +807,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + final ChatType.Bound chatType = ChatType.bind(chatTypeKey, this.player.level().registryAccess(), PaperAdventure.asVanilla(displayName(player))); + + OutgoingChat outgoingChat = viewers instanceof LazyChatAudienceSet lazyAudienceSet && lazyAudienceSet.isLazy() ? new ServerOutgoingChat() : new ViewersOutgoingChat(); -+ /* if (this.flags.get(FORCE_PREVIEW_USE)) { // TODO (future, maybe?) -+ outgoingChat.sendOriginal(player, viewers, chatType); -+ } else */ + if (this.flags.get(FORMAT_CHANGED)) { + if (renderer instanceof ChatRenderer.ViewerUnaware unaware) { + outgoingChat.sendFormatChangedViewerUnaware(player, PaperAdventure.asVanilla(unaware.render(player, displayName, message)), viewers, chatType); @@ -1004,7 +815,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } else if (this.flags.get(MESSAGE_CHANGED)) { + if (!(renderer instanceof ChatRenderer.ViewerUnaware unaware)) { -+ throw new IllegalStateException("BUG: There should not be a non-legacy renderer at this point"); ++ throw new IllegalStateException("BUG: This should be a ViewerUnaware renderer at this point"); + } + final Component renderedComponent = chatTypeKey == ChatType.CHAT ? message : unaware.render(player, displayName, message); + outgoingChat.sendMessageChanged(player, PaperAdventure.asVanilla(renderedComponent), viewers, chatType); @@ -1085,7 +896,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + }); + + private net.kyori.adventure.chat.ChatType.Bound adventure(ChatType.Bound chatType) { -+ final String stringKey = Objects.requireNonNull( ++ @Subst("key:value") final String stringKey = Objects.requireNonNull( + ChatProcessor.this.server.registryAccess().registryOrThrow(Registries.CHAT_TYPE).getKey(chatType.chatType()), + () -> "No key for '%s' in CHAT_TYPE registry.".formatted(chatType) + ).toString(); @@ -1213,6 +1024,67 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return LegacyComponentSerializer.legacySection().serialize(player.adventure$displayName); + } +} +diff --git a/src/main/java/io/papermc/paper/adventure/ImprovedChatDecorator.java b/src/main/java/io/papermc/paper/adventure/ImprovedChatDecorator.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/adventure/ImprovedChatDecorator.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.adventure; ++ ++import io.papermc.paper.event.player.AsyncChatCommandDecorateEvent; ++import io.papermc.paper.event.player.AsyncChatDecorateEvent; ++import java.util.concurrent.CompletableFuture; ++import net.minecraft.commands.CommandSourceStack; ++import net.minecraft.network.chat.ChatDecorator; ++import net.minecraft.network.chat.Component; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ServerPlayer; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++@DefaultQualifier(NonNull.class) ++public final class ImprovedChatDecorator implements ChatDecorator { ++ private final MinecraftServer server; ++ ++ public ImprovedChatDecorator(final MinecraftServer server) { ++ this.server = server; ++ } ++ ++ @Override ++ public CompletableFuture decorate(final @Nullable ServerPlayer sender, final Component message) { ++ return decorate(this.server, sender, null, message); ++ } ++ ++ @Override ++ public CompletableFuture decorate(final @Nullable ServerPlayer sender, final @Nullable CommandSourceStack commandSourceStack, final Component message) { ++ return decorate(this.server, sender, commandSourceStack, message); ++ } ++ ++ private static CompletableFuture decorate(final MinecraftServer server, final @Nullable ServerPlayer player, final @Nullable CommandSourceStack commandSourceStack, final Component originalMessage) { ++ return CompletableFuture.supplyAsync(() -> { ++ final net.kyori.adventure.text.Component initialResult = PaperAdventure.asAdventure(originalMessage); ++ ++ final @Nullable CraftPlayer craftPlayer = player == null ? null : player.getBukkitEntity(); ++ ++ final AsyncChatDecorateEvent event; ++ if (commandSourceStack != null) { ++ // TODO more command decorate context ++ event = new AsyncChatCommandDecorateEvent(craftPlayer, initialResult); ++ } else { ++ event = new AsyncChatDecorateEvent(craftPlayer, initialResult); ++ } ++ ++ if (event.callEvent()) { ++ return PaperAdventure.asVanilla(event.result()); ++ } ++ ++ return originalMessage; ++ }, server.chatExecutor); ++ } ++} 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 @@ -1401,7 +1273,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + // Component + -+ public static Component asAdventure(final net.minecraft.network.chat.Component component) { ++ public static @NotNull Component asAdventure(@Nullable final net.minecraft.network.chat.Component component) { + return component == null ? Component.empty() : WRAPPER_AWARE_SERIALIZER.deserialize(component); + } + @@ -1429,7 +1301,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return jsons; + } + -+ public static net.minecraft.network.chat.Component asVanilla(final Component component) { ++ public static net.minecraft.network.chat.Component asVanilla(@Nullable final Component component) { + if (component == null) return null; + if (true) return new AdventureComponent(component); + return WRAPPER_AWARE_SERIALIZER.serialize(component); @@ -2142,9 +2014,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - source.getChatMessageChainer().append(completableFuture, filtered -> { - PlayerChatMessage playerChatMessage2 = message.withUnsignedContent(component).filter(filtered.mask()); + // Paper start - support asynchronous chat decoration -+ CompletableFuture componentFuture = minecraftServer.getChatDecorator().decorate(source.getPlayer(), source, message.decoratedContent()); ++ CompletableFuture componentFuture = minecraftServer.getChatDecorator().decorate(source.getPlayer(), source, message.decoratedContent()); + source.getChatMessageChainer().append(CompletableFuture.allOf(completableFuture, componentFuture), filtered -> { -+ PlayerChatMessage playerChatMessage2 = message.withUnsignedContent(componentFuture.join().component()).filter(completableFuture.join().mask()); ++ PlayerChatMessage playerChatMessage2 = message.withUnsignedContent(componentFuture.join()).filter(completableFuture.join().mask()); + // Paper end - support asynchronous chat decoration callback.accept(playerChatMessage2); }); @@ -2155,8 +2027,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - Component component = chatDecorator.decorate(source.getPlayer(), message.decoratedContent()); - callback.accept(message.withUnsignedContent(component)); + // Paper start - support asynchronous chat decoration -+ CompletableFuture componentFuture = chatDecorator.decorate(source.getPlayer(), source, message.decoratedContent()); -+ source.getChatMessageChainer().append(componentFuture, (result) -> callback.accept(message.withUnsignedContent(result.component()))); ++ CompletableFuture componentFuture = chatDecorator.decorate(source.getPlayer(), source, message.decoratedContent()); ++ source.getChatMessageChainer().append(componentFuture, (result) -> callback.accept(message.withUnsignedContent(result))); + // Paper end - support asynchronous chat decoration } @@ -2249,68 +2121,16 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @FunctionalInterface public interface ChatDecorator { - ChatDecorator PLAIN = (sender, message) -> message; -+ ChatDecorator PLAIN = (sender, message) -> CompletableFuture.completedFuture(message); // Paper - adventure; support async chat decoration events; ++ ChatDecorator PLAIN = (sender, message) -> CompletableFuture.completedFuture(message); // Paper - adventure; support async chat decoration events - Component decorate(@Nullable ServerPlayer sender, Component message); -+ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - adventure; support chat decoration events ++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - adventure; support chat decoration events (callers should use the overload with CommandSourceStack) + CompletableFuture decorate(@Nullable ServerPlayer sender, Component message); // Paper - adventure; support async chat decoration events + + // Paper start - adventure; support async chat decoration events -+ default CompletableFuture decorate(@Nullable ServerPlayer sender, @Nullable net.minecraft.commands.CommandSourceStack commandSourceStack, Component message) { ++ default CompletableFuture decorate(@Nullable ServerPlayer sender, @Nullable net.minecraft.commands.CommandSourceStack commandSourceStack, Component message) { + throw new UnsupportedOperationException("Must override this implementation"); + } -+ -+ static ChatDecorator create(ImprovedChatDecorator delegate) { -+ return new ChatDecorator() { -+ @Override -+ public CompletableFuture decorate(@Nullable ServerPlayer sender, Component message) { -+ return this.decorate(sender, null, message).thenApply(Result::component); -+ } -+ -+ @Override -+ public CompletableFuture decorate(@Nullable ServerPlayer sender, @Nullable net.minecraft.commands.CommandSourceStack commandSourceStack, Component message) { -+ return delegate.decorate(sender, commandSourceStack, message); -+ } -+ }; -+ } -+ -+ @FunctionalInterface -+ interface ImprovedChatDecorator { -+ CompletableFuture decorate(@Nullable ServerPlayer sender, @Nullable net.minecraft.commands.CommandSourceStack commandSourceStack, Component message); -+ } -+ -+ interface Result { -+ boolean hasNoFormatting(); -+ -+ Component component(); -+ -+ MessagePair message(); -+ -+ boolean modernized(); -+ } -+ -+ record MessagePair(net.kyori.adventure.text.Component component, String legacyMessage) { } -+ -+ record LegacyResult(Component component, String format, MessagePair message, boolean hasNoFormatting, boolean modernized) implements Result { -+ public LegacyResult(net.kyori.adventure.text.Component component, String format, MessagePair message, boolean hasNoFormatting, boolean modernified) { -+ this(io.papermc.paper.adventure.PaperAdventure.asVanilla(component), format, message, hasNoFormatting, modernified); -+ } -+ public LegacyResult { -+ component = component instanceof io.papermc.paper.adventure.AdventureComponent adventureComponent ? adventureComponent.deepConverted() : component; -+ } -+ } -+ -+ record ModernResult(Component component, boolean hasNoFormatting, boolean modernized) implements Result { -+ public ModernResult(net.kyori.adventure.text.Component component, boolean hasNoFormatting, boolean modernized) { -+ this(io.papermc.paper.adventure.PaperAdventure.asVanilla(component), hasNoFormatting, modernized); -+ } -+ -+ @Override -+ public MessagePair message() { -+ final net.kyori.adventure.text.Component adventureComponent = io.papermc.paper.adventure.PaperAdventure.WRAPPER_AWARE_SERIALIZER.deserialize(this.component); -+ return new MessagePair(adventureComponent, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(adventureComponent)); -+ } -+ } + // Paper end - adventure; support async chat decoration events } diff --git a/src/main/java/net/minecraft/network/chat/ComponentSerialization.java b/src/main/java/net/minecraft/network/chat/ComponentSerialization.java @@ -2455,24 +2275,11 @@ diff --git a/src/main/java/net/minecraft/network/chat/PlayerChatMessage.java b/s index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/network/chat/PlayerChatMessage.java +++ b/src/main/java/net/minecraft/network/chat/PlayerChatMessage.java -@@ -0,0 +0,0 @@ import net.minecraft.util.SignatureUpdater; - import net.minecraft.util.SignatureValidator; - +@@ -0,0 +0,0 @@ import net.minecraft.util.SignatureValidator; public record PlayerChatMessage( -- SignedMessageLink link, @Nullable MessageSignature signature, SignedMessageBody signedBody, @Nullable Component unsignedContent, FilterMask filterMask -+ SignedMessageLink link, @Nullable MessageSignature signature, SignedMessageBody signedBody, @Nullable Component unsignedContent, FilterMask filterMask, @Nullable net.minecraft.network.chat.ChatDecorator.Result result // Paper - adventure; support signed messages + SignedMessageLink link, @Nullable MessageSignature signature, SignedMessageBody signedBody, @Nullable Component unsignedContent, FilterMask filterMask ) { + // Paper start - adventure; support signed messages -+ public PlayerChatMessage(SignedMessageLink link, @Nullable MessageSignature signature, SignedMessageBody signedBody, @Nullable Component unsignedContent, FilterMask filterMask) { -+ this(link, signature, signedBody, unsignedContent, filterMask, null); -+ } -+ public PlayerChatMessage withResult(net.minecraft.network.chat.ChatDecorator.Result result) { -+ final PlayerChatMessage msg = this.withUnsignedContent(result.component()); -+ return new PlayerChatMessage(msg.link, msg.signature, msg.signedBody, msg.unsignedContent, msg.filterMask, result); -+ } -+ public net.minecraft.network.chat.ChatDecorator.Result requireResult() { -+ return Objects.requireNonNull(this.result, "Requires a decoration result to be set here"); -+ } + public final class AdventureView implements net.kyori.adventure.chat.SignedMessage { + private AdventureView() { + } @@ -2516,7 +2323,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 public PlayerChatMessage withUnsignedContent(Component unsignedContent) { - Component component = !unsignedContent.equals(Component.literal(this.signedContent())) ? unsignedContent : null; -+ Component component = !(unsignedContent instanceof io.papermc.paper.adventure.AdventureComponent advComponent ? advComponent.deepConverted() : unsignedContent).equals(Component.literal(this.signedContent())) ? unsignedContent : null; // Paper - adventure; convert adventure component wraps ++ // Paper start - adventure ++ final Component component; ++ if (unsignedContent instanceof io.papermc.paper.adventure.AdventureComponent advComponent) { ++ component = this.signedContent().equals(io.papermc.paper.adventure.AdventureCodecs.tryCollapseToString(advComponent.adventure$component())) ? null : unsignedContent; ++ } else { ++ component = !unsignedContent.equals(Component.literal(this.signedContent())) ? unsignedContent : null; ++ } ++ // Paper end - adventure return new PlayerChatMessage(this.link, this.signature, this.signedBody, component, this.filterMask); } @@ -2747,14 +2561,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - // CraftBukkit end + new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Chat Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); // Paper ++ public final ChatDecorator improvedChatDecorator = new io.papermc.paper.adventure.ImprovedChatDecorator(this); // Paper - adventure public ChatDecorator getChatDecorator() { - return ChatDecorator.PLAIN; -+ // Paper start - moved to ChatPreviewProcessor -+ return ChatDecorator.create((sender, commandSourceStack, message) -> { -+ final io.papermc.paper.adventure.ChatDecorationProcessor processor = new io.papermc.paper.adventure.ChatDecorationProcessor(this, sender, commandSourceStack, message); -+ return processor.process(); -+ }); -+ // Paper end ++ return this.improvedChatDecorator; // Paper - support async chat decoration events } public boolean logIPs() { @@ -2987,12 +2797,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 CompletableFuture completablefuture = this.filterTextPacket(playerchatmessage.signedContent()).thenApplyAsync(Function.identity(), this.server.chatExecutor); // CraftBukkit - async chat - Component ichatbasecomponent = this.server.getChatDecorator().decorate(this.player, playerchatmessage.decoratedContent()); -+ CompletableFuture componentFuture = this.server.getChatDecorator().decorate(this.player, null, playerchatmessage.decoratedContent()); // Paper ++ CompletableFuture componentFuture = this.server.getChatDecorator().decorate(this.player, null, playerchatmessage.decoratedContent()); // Paper - Adventure - this.chatMessageChain.append(completablefuture, (filteredtext) -> { - PlayerChatMessage playerchatmessage1 = playerchatmessage.withUnsignedContent(ichatbasecomponent).filter(filteredtext.mask()); -+ this.chatMessageChain.append(CompletableFuture.allOf(completablefuture, componentFuture), (filteredtext) -> { -+ PlayerChatMessage playerchatmessage1 = playerchatmessage.filter(completablefuture.join().mask()).withResult(componentFuture.join()); // Paper ++ this.chatMessageChain.append(CompletableFuture.allOf(completablefuture, componentFuture), (filteredtext) -> { // Paper - Adventure ++ PlayerChatMessage playerchatmessage1 = playerchatmessage.withUnsignedContent(componentFuture.join()).filter(completablefuture.join().mask()); // Paper - Adventure this.broadcastChatMessage(playerchatmessage1); }); diff --git a/patches/server/Improve-Player-chat-API-handling.patch b/patches/server/Improve-Player-chat-API-handling.patch index 3844bee337..ec17e80cb3 100644 --- a/patches/server/Improve-Player-chat-API-handling.patch +++ b/patches/server/Improve-Player-chat-API-handling.patch @@ -68,7 +68,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + if (msg.startsWith("/")) { + this.getHandle().connection.handleCommand(msg); + } else { -+ final PlayerChatMessage playerChatMessage = PlayerChatMessage.system(msg).withResult(new net.minecraft.network.chat.ChatDecorator.ModernResult(Component.literal(msg), true, false)); ++ final PlayerChatMessage playerChatMessage = PlayerChatMessage.system(msg).withUnsignedContent(Component.literal(msg)); + // TODO chat decorating + // TODO text filtering + this.getHandle().connection.chat(msg, playerChatMessage, false); diff --git a/patches/server/Option-to-use-vanilla-per-world-scoreboard-coloring-.patch b/patches/server/Option-to-use-vanilla-per-world-scoreboard-coloring-.patch index 574dbdd3f9..9ee8d98d99 100644 --- a/patches/server/Option-to-use-vanilla-per-world-scoreboard-coloring-.patch +++ b/patches/server/Option-to-use-vanilla-per-world-scoreboard-coloring-.patch @@ -15,10 +15,10 @@ 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.kyori.adventure.audience.Audience; - import net.kyori.adventure.audience.ForwardingAudience; +@@ -0,0 +0,0 @@ import net.kyori.adventure.audience.ForwardingAudience; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; + import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.minecraft.ChatFormatting; import net.minecraft.Optionull; import net.minecraft.Util;