|
|
|
@ -251,28 +251,33 @@ index 0000000000000000000000000000000000000000..e7671b9e2fc5ed01461e4ae1557dfc14
|
|
|
|
|
+}
|
|
|
|
|
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..8b7b140ac0326ffccd51684e283647148de30647
|
|
|
|
|
index 0000000000000000000000000000000000000000..4dfeb712fffc28f79e8b6c13869f3d0e64fc9ccd
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java
|
|
|
|
|
@@ -0,0 +1,373 @@
|
|
|
|
|
@@ -0,0 +1,410 @@
|
|
|
|
|
+package io.papermc.paper.adventure;
|
|
|
|
|
+
|
|
|
|
|
+import com.google.common.base.Suppliers;
|
|
|
|
|
+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;
|
|
|
|
|
+import java.lang.reflect.Field;
|
|
|
|
|
+import java.lang.reflect.Modifier;
|
|
|
|
|
+import java.util.BitSet;
|
|
|
|
|
+import java.util.Collection;
|
|
|
|
|
+import java.util.HashMap;
|
|
|
|
|
+import java.util.HashSet;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.Objects;
|
|
|
|
|
+import java.util.Set;
|
|
|
|
|
+import java.util.concurrent.ExecutionException;
|
|
|
|
|
+import java.util.function.Function;
|
|
|
|
|
+import java.util.function.Supplier;
|
|
|
|
|
+import net.kyori.adventure.audience.Audience;
|
|
|
|
|
+import net.kyori.adventure.audience.MessageType;
|
|
|
|
|
+import net.kyori.adventure.audience.ForwardingAudience;
|
|
|
|
|
+import net.kyori.adventure.key.Key;
|
|
|
|
|
+import net.kyori.adventure.text.Component;
|
|
|
|
|
+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;
|
|
|
|
@ -280,7 +285,6 @@ index 0000000000000000000000000000000000000000..8b7b140ac0326ffccd51684e28364714
|
|
|
|
|
+import net.minecraft.resources.ResourceKey;
|
|
|
|
|
+import net.minecraft.server.MinecraftServer;
|
|
|
|
|
+import net.minecraft.server.level.ServerPlayer;
|
|
|
|
|
+import org.bukkit.command.CommandSender;
|
|
|
|
|
+import org.bukkit.command.ConsoleCommandSender;
|
|
|
|
|
+import org.bukkit.craftbukkit.entity.CraftPlayer;
|
|
|
|
|
+import org.bukkit.craftbukkit.util.LazyPlayerSet;
|
|
|
|
@ -524,39 +528,79 @@ index 0000000000000000000000000000000000000000..8b7b140ac0326ffccd51684e28364714
|
|
|
|
|
+ final class ViewersOutgoingChat implements OutgoingChat {
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void sendFormatChangedViewerAware(CraftPlayer player, Component displayName, Component message, ChatRenderer renderer, Set<Audience> viewers, ChatType.Bound chatType) {
|
|
|
|
|
+ this.broadcastToViewers(viewers, player, chatType, v -> PaperAdventure.asVanilla(renderer.render(player, displayName, message, v)));
|
|
|
|
|
+ this.broadcastToViewers(viewers, chatType, v -> PaperAdventure.asVanilla(renderer.render(player, displayName, message, v)));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void sendMessageChanged(CraftPlayer player, net.minecraft.network.chat.Component renderedMessage, Set<Audience> viewers, ChatType.Bound chatType) {
|
|
|
|
|
+ this.broadcastToViewers(viewers, player, chatType, new ConstantFunction(renderedMessage));
|
|
|
|
|
+ this.broadcastToViewers(viewers, chatType, $ -> renderedMessage);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void sendOriginal(CraftPlayer player, Set<Audience> viewers, ChatType.Bound chatType) {
|
|
|
|
|
+ this.broadcastToViewers(viewers, player, chatType, null);
|
|
|
|
|
+ this.broadcastToViewers(viewers, chatType, null);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void broadcastToViewers(Collection<Audience> viewers, final Player source, final ChatType.Bound chatType, final @Nullable Function<Audience, net.minecraft.network.chat.Component> msgFunction) {
|
|
|
|
|
+ final Supplier<Component> fallbackSupplier = Suppliers.memoize(() -> PaperAdventure.asAdventure(msgFunction instanceof ConstantFunction constantFunction ? constantFunction.component : ChatProcessor.this.message.decoratedContent()));
|
|
|
|
|
+ final Function<Audience, Component> audienceMsgFunction = !(msgFunction instanceof ConstantFunction || msgFunction == null) ? msgFunction.andThen(PaperAdventure::asAdventure) : viewer -> fallbackSupplier.get();
|
|
|
|
|
+ private void broadcastToViewers(Collection<Audience> viewers, final ChatType.Bound chatType, final @Nullable Function<Audience, net.minecraft.network.chat.Component> msgFunction) {
|
|
|
|
|
+ for (Audience viewer : viewers) {
|
|
|
|
|
+ if (viewer instanceof Player || viewer instanceof ConsoleCommandSender) {
|
|
|
|
|
+ // players and console have builtin PlayerChatMessage sending support while other audiences do not
|
|
|
|
|
+ this.sendToViewer((CommandSender) viewer, chatType, msgFunction);
|
|
|
|
|
+ if (acceptsNative(viewer)) {
|
|
|
|
|
+ this.sendNative(viewer, chatType, msgFunction);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ viewer.sendMessage(source, audienceMsgFunction.apply(viewer), MessageType.CHAT);
|
|
|
|
|
+ final net.minecraft.network.chat.@Nullable Component unsigned = Util.mapNullable(msgFunction, f -> f.apply(viewer));
|
|
|
|
|
+ final PlayerChatMessage msg = unsigned == null ? ChatProcessor.this.message : ChatProcessor.this.message.withUnsignedContent(unsigned);
|
|
|
|
|
+ viewer.sendMessage(msg.adventureView(), this.adventure(chatType));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void sendToViewer(final CommandSender viewer, final ChatType.Bound chatType, final @Nullable Function<Audience, net.minecraft.network.chat.Component> msgFunction) {
|
|
|
|
|
+ private static final Map<String, net.kyori.adventure.chat.ChatType> BUILT_IN_CHAT_TYPES = Util.make(() -> {
|
|
|
|
|
+ final Map<String, net.kyori.adventure.chat.ChatType> map = new HashMap<>();
|
|
|
|
|
+ for (final Field declaredField : net.kyori.adventure.chat.ChatType.class.getDeclaredFields()) {
|
|
|
|
|
+ if (Modifier.isStatic(declaredField.getModifiers()) && declaredField.getType().equals(ChatType.class)) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ final net.kyori.adventure.chat.ChatType type = (net.kyori.adventure.chat.ChatType) declaredField.get(null);
|
|
|
|
|
+ map.put(type.key().asString(), type);
|
|
|
|
|
+ } catch (final ReflectiveOperationException ignore) {
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return map;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ private net.kyori.adventure.chat.ChatType.Bound adventure(ChatType.Bound chatType) {
|
|
|
|
|
+ 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();
|
|
|
|
|
+ net.kyori.adventure.chat.@Nullable ChatType adventure = BUILT_IN_CHAT_TYPES.get(stringKey);
|
|
|
|
|
+ if (adventure == null) {
|
|
|
|
|
+ adventure = net.kyori.adventure.chat.ChatType.chatType(Key.key(stringKey));
|
|
|
|
|
+ }
|
|
|
|
|
+ return adventure.bind(
|
|
|
|
|
+ PaperAdventure.asAdventure(chatType.name()),
|
|
|
|
|
+ PaperAdventure.asAdventure(chatType.targetName())
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static boolean acceptsNative(final Audience viewer) {
|
|
|
|
|
+ if (viewer instanceof Player || viewer instanceof ConsoleCommandSender) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (viewer instanceof ForwardingAudience.Single single) {
|
|
|
|
|
+ return acceptsNative(single.audience());
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void sendNative(final Audience viewer, final ChatType.Bound chatType, final @Nullable Function<Audience, net.minecraft.network.chat.Component> msgFunction) {
|
|
|
|
|
+ if (viewer instanceof ConsoleCommandSender) {
|
|
|
|
|
+ this.sendToServer(chatType, msgFunction);
|
|
|
|
|
+ } else if (viewer instanceof CraftPlayer craftPlayer) {
|
|
|
|
|
+ craftPlayer.getHandle().sendChatMessage(ChatProcessor.this.outgoing, ChatProcessor.this.player.shouldFilterMessageTo(craftPlayer.getHandle()), chatType, Util.mapNullable(msgFunction, f -> f.apply(viewer)));
|
|
|
|
|
+ } else if (viewer instanceof ForwardingAudience.Single single) {
|
|
|
|
|
+ this.sendNative(single.audience(), chatType, msgFunction);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ throw new IllegalStateException("Should only be a Player or Console");
|
|
|
|
|
+ throw new IllegalStateException("Should only be a Player or Console or ForwardingAudience.Single pointing to one!");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
@ -564,13 +608,6 @@ index 0000000000000000000000000000000000000000..8b7b140ac0326ffccd51684e28364714
|
|
|
|
|
+ final PlayerChatMessage toConsoleMessage = msgFunction == null ? ChatProcessor.this.message : ChatProcessor.this.message.withUnsignedContent(msgFunction.apply(ChatProcessor.this.server.console));
|
|
|
|
|
+ ChatProcessor.this.server.logChatMessage(toConsoleMessage.decoratedContent(), chatType, ChatProcessor.this.server.getPlayerList().verifyChatTrusted(toConsoleMessage) ? null : "Not Secure");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ record ConstantFunction(net.minecraft.network.chat.Component component) implements Function<Audience, net.minecraft.network.chat.Component> {
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public net.minecraft.network.chat.Component apply(Audience audience) {
|
|
|
|
|
+ return this.component;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Set<Audience> viewersFromLegacy(final Set<Player> recipients) {
|
|
|
|
@ -1669,6 +1706,25 @@ index ed7e1a6fc745df745d5bc79623948bb1015c9252..9d0d5a44c5948bde037165147d18aaab
|
|
|
|
|
MutableComponent mutableComponent = text.getContents().resolve(source, sender, depth + 1);
|
|
|
|
|
|
|
|
|
|
for(Component component : text.getSiblings()) {
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/network/chat/MessageSignature.java b/src/main/java/net/minecraft/network/chat/MessageSignature.java
|
|
|
|
|
index df9997873c4bbec184379ec14dca1bf4566eb89d..cc342518c26096aaaec0020a5d38cd63ff823019 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/network/chat/MessageSignature.java
|
|
|
|
|
+++ b/src/main/java/net/minecraft/network/chat/MessageSignature.java
|
|
|
|
|
@@ -12,12 +12,12 @@ import net.minecraft.util.ExtraCodecs;
|
|
|
|
|
import net.minecraft.util.SignatureUpdater;
|
|
|
|
|
import net.minecraft.util.SignatureValidator;
|
|
|
|
|
|
|
|
|
|
-public record MessageSignature(byte[] bytes) {
|
|
|
|
|
+public record MessageSignature(byte[] bytes) implements net.kyori.adventure.chat.SignedMessage.Signature { // Paper
|
|
|
|
|
public static final Codec<MessageSignature> CODEC = ExtraCodecs.BASE64_STRING.xmap(MessageSignature::new, MessageSignature::bytes);
|
|
|
|
|
public static final int BYTES = 256;
|
|
|
|
|
|
|
|
|
|
public MessageSignature {
|
|
|
|
|
- Preconditions.checkState(bs.length == 256, "Invalid message signature size");
|
|
|
|
|
+ Preconditions.checkState(bytes.length == 256, "Invalid message signature size"); // Paper - decompile fix
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static MessageSignature read(FriendlyByteBuf buf) {
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/network/chat/OutgoingChatMessage.java b/src/main/java/net/minecraft/network/chat/OutgoingChatMessage.java
|
|
|
|
|
index f8773f2982e6cd40661d138a7c32f219cda9225c..74cf1c043beef03cfd5adf481414a5ee78bef2a6 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/network/chat/OutgoingChatMessage.java
|
|
|
|
@ -1715,10 +1771,10 @@ index f8773f2982e6cd40661d138a7c32f219cda9225c..74cf1c043beef03cfd5adf481414a5ee
|
|
|
|
|
sender.connection.sendPlayerChatMessage(playerChatMessage, params);
|
|
|
|
|
}
|
|
|
|
|
diff --git a/src/main/java/net/minecraft/network/chat/PlayerChatMessage.java b/src/main/java/net/minecraft/network/chat/PlayerChatMessage.java
|
|
|
|
|
index b3a44ed8f365daf1031d46d879c84d2ea15cd951..3c7276598acb3711f8ceccaad95b30c72ea87d77 100644
|
|
|
|
|
index b3a44ed8f365daf1031d46d879c84d2ea15cd951..6a5fa03301c47ac3ea3637fd41de8a33702f18eb 100644
|
|
|
|
|
--- a/src/main/java/net/minecraft/network/chat/PlayerChatMessage.java
|
|
|
|
|
+++ b/src/main/java/net/minecraft/network/chat/PlayerChatMessage.java
|
|
|
|
|
@@ -15,7 +15,19 @@ import net.minecraft.util.ExtraCodecs;
|
|
|
|
|
@@ -15,7 +15,53 @@ import net.minecraft.util.ExtraCodecs;
|
|
|
|
|
import net.minecraft.util.SignatureUpdater;
|
|
|
|
|
import net.minecraft.util.SignatureValidator;
|
|
|
|
|
|
|
|
|
@ -1735,11 +1791,45 @@ index b3a44ed8f365daf1031d46d879c84d2ea15cd951..3c7276598acb3711f8ceccaad95b30c7
|
|
|
|
|
+ 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() {
|
|
|
|
|
+ }
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public @org.jetbrains.annotations.NotNull Instant timestamp() {
|
|
|
|
|
+ return PlayerChatMessage.this.timeStamp();
|
|
|
|
|
+ }
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public long salt() {
|
|
|
|
|
+ return PlayerChatMessage.this.salt();
|
|
|
|
|
+ }
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public @org.jetbrains.annotations.Nullable Signature signature() {
|
|
|
|
|
+ return PlayerChatMessage.this.signature();
|
|
|
|
|
+ }
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public net.kyori.adventure.text.@org.jetbrains.annotations.Nullable Component unsignedContent() {
|
|
|
|
|
+ return PlayerChatMessage.this.unsignedContent() == null ? null : io.papermc.paper.adventure.PaperAdventure.asAdventure(PlayerChatMessage.this.unsignedContent());
|
|
|
|
|
+ }
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public @org.jetbrains.annotations.NotNull String message() {
|
|
|
|
|
+ return PlayerChatMessage.this.signedContent();
|
|
|
|
|
+ }
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public @org.jetbrains.annotations.NotNull net.kyori.adventure.identity.Identity identity() {
|
|
|
|
|
+ return net.kyori.adventure.identity.Identity.identity(PlayerChatMessage.this.sender());
|
|
|
|
|
+ }
|
|
|
|
|
+ public PlayerChatMessage playerChatMessage() {
|
|
|
|
|
+ return PlayerChatMessage.this;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ public AdventureView adventureView() {
|
|
|
|
|
+ return new AdventureView();
|
|
|
|
|
+ }
|
|
|
|
|
+ // Paper end
|
|
|
|
|
public static final MapCodec<PlayerChatMessage> MAP_CODEC = RecordCodecBuilder.mapCodec((instance) -> {
|
|
|
|
|
return instance.group(SignedMessageLink.CODEC.fieldOf("link").forGetter(PlayerChatMessage::link), MessageSignature.CODEC.optionalFieldOf("signature").forGetter((message) -> {
|
|
|
|
|
return Optional.ofNullable(message.signature);
|
|
|
|
|
@@ -40,7 +52,7 @@ public record PlayerChatMessage(SignedMessageLink link, @Nullable MessageSignatu
|
|
|
|
|
@@ -40,7 +86,7 @@ public record PlayerChatMessage(SignedMessageLink link, @Nullable MessageSignatu
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public PlayerChatMessage withUnsignedContent(Component unsignedContent) {
|
|
|
|
@ -3184,7 +3274,7 @@ index 446fdca49a5a6999626a7ee3a1d5c168b15a09dd..f9863e138994f6c7a7975a852f106faa
|
|
|
|
|
public boolean isOp() {
|
|
|
|
|
return true;
|
|
|
|
|
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
|
|
|
index d2be98416529aea3bdbedd0ea7131bd8de2a0162..6c316c969e87d9da047cd80c15d5579f7f65d1a5 100644
|
|
|
|
|
index d2be98416529aea3bdbedd0ea7131bd8de2a0162..6bf9efe1f6c06fd2adaf68cd360482f252903c8c 100644
|
|
|
|
|
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
|
|
|
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
|
|
|
|
|
@@ -277,14 +277,39 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
|
|
|
@ -3410,7 +3500,7 @@ index d2be98416529aea3bdbedd0ea7131bd8de2a0162..6c316c969e87d9da047cd80c15d5579f
|
|
|
|
|
@Override
|
|
|
|
|
public int getPing() {
|
|
|
|
|
return this.getHandle().latency;
|
|
|
|
|
@@ -1955,6 +2048,250 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
|
|
|
|
@@ -1955,6 +2048,254 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
|
|
|
|
|
return this.getHandle().allowsListing();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -3456,6 +3546,10 @@ index d2be98416529aea3bdbedd0ea7131bd8de2a0162..6c316c969e87d9da047cd80c15d5579f
|
|
|
|
|
+ public void sendMessage(net.kyori.adventure.chat.SignedMessage signedMessage, net.kyori.adventure.chat.ChatType.Bound boundChatType) {
|
|
|
|
|
+ if (getHandle().connection == null) return;
|
|
|
|
|
+
|
|
|
|
|
+ if (signedMessage instanceof PlayerChatMessage.AdventureView view) {
|
|
|
|
|
+ this.getHandle().sendChatMessage(net.minecraft.network.chat.OutgoingChatMessage.create(view.playerChatMessage()), this.getHandle().isTextFilteringEnabled(), this.toHandle(boundChatType));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ net.kyori.adventure.text.Component message = signedMessage.unsignedContent() == null ? net.kyori.adventure.text.Component.text(signedMessage.message()) : signedMessage.unsignedContent();
|
|
|
|
|
+ if (signedMessage.isSystem()) {
|
|
|
|
|
+ this.sendMessage(message, boundChatType);
|
|
|
|
|