ChatProcessor fixes (#8690)

Fixes handling for `ForwardingAudience.Single` and passes the signed message to non-native `Audience` types
This commit is contained in:
Jason 2022-12-16 23:05:48 -07:00 committed by GitHub
parent 44dc5a4c26
commit 86ed0d596a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 139 additions and 46 deletions

View File

@ -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);

View File

@ -1816,10 +1816,10 @@ index b0ffa23faf62629043dfd613315eaf9c5fcc2cfe..00000000000000000000000000000000
- }
-}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 6c316c969e87d9da047cd80c15d5579f7f65d1a5..2da35b1caef3958c7a385012d85e65f292c88227 100644
index 6bf9efe1f6c06fd2adaf68cd360482f252903c8c..a7609957da493c5e79771f9ff48e94acadca8bfc 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -2378,6 +2378,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
@@ -2382,6 +2382,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
CraftPlayer.this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundSystemChatPacket(components, position == net.md_5.bungee.api.ChatMessageType.ACTION_BAR));
}

View File

@ -12,21 +12,20 @@ for this on CB at one point but I can't find it. We may need to do this
ourselves at some point in the future.
diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java
index 8b7b140ac0326ffccd51684e283647148de30647..fd5cbb45e9894b7895ea488e2b9876c0391f6f11 100644
index 4dfeb712fffc28f79e8b6c13869f3d0e64fc9ccd..0f5cf192c0848bdeadeda381e19ec035455abfe5 100644
--- a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java
+++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java
@@ -23,8 +23,10 @@ import net.minecraft.network.chat.PlayerChatMessage;
@@ -28,7 +28,9 @@ import net.minecraft.network.chat.PlayerChatMessage;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
+import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
+import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.craftbukkit.util.LazyPlayerSet;
import org.bukkit.craftbukkit.util.Waitable;
@@ -326,10 +328,16 @@ public final class ChatProcessor {
@@ -363,10 +365,16 @@ public final class ChatProcessor {
}
static String legacyDisplayName(final CraftPlayer player) {

View File

@ -5,10 +5,10 @@ Subject: [PATCH] Expose attack cooldown methods for Player
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 5057a65e1f9b4f5646db83b4311636c963c39515..7465d8de698d9eed71c96019fc55d6efdd13269c 100644
index e9c8148495dcea8c6ba143ee2d5bd0430a5c94dd..1ce6638a98acbfadcde649363d6703d050e377fe 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -2649,6 +2649,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
@@ -2653,6 +2653,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
return this.adventure$pointers;
}

View File

@ -12,10 +12,10 @@ Player we will look at limiting the scope of this change. It appears to
be unintentional in the few cases we've seen so far.
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index e693b7c94bd0c35260969266a7fa9c606d91f5b4..b238b8ae5fd53c109ee3437a8a9b6a018a62da39 100644
index 9c8b9829ab06c45ae8fdd921118ed20077a9655b..77a68059288835619ce78ada0cdadc3d6bd761f4 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -2696,6 +2696,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
@@ -2700,6 +2700,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
public void resetCooldown() {
getHandle().resetAttackStrengthTicker();
}

View File

@ -56,10 +56,10 @@ index c88e6ca7ee28ebaf235e11bbe70b13c90508924e..39fe98d65f3029efa2f54c2b6dbf1e53
return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper
}
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index c7629ce42389d7ae15516f94cbca636181e62c88..08d1dc46190a6cfea869ca247e675e5996969a0b 100644
index 7c9c31073c052df76d5f209a23896c4b020413ff..e807cfbeecce04e87d30652a58e9eba372b9d8aa 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -2824,6 +2824,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
@@ -2828,6 +2828,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
// Paper end
};

View File

@ -5,10 +5,10 @@ Subject: [PATCH] Elder Guardian appearance API
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index f872f0e955b11cc50c239f98e02dfac47908d8c7..968892831bcbfd40a0630018f9a5dd2ff2761836 100644
index cd439593d050fb157fae3575207a285a48d71783..009150ece99c039c7e92ac89c8069a2e378db34a 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -2996,6 +2996,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
@@ -3000,6 +3000,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
}
// Paper end

View File

@ -10,10 +10,10 @@ public net.minecraft.world.entity.monster.warden.WardenSpawnTracker cooldownTick
public net.minecraft.world.entity.monster.warden.WardenSpawnTracker increaseWarningLevel()V
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
index 968892831bcbfd40a0630018f9a5dd2ff2761836..f8ba66e6711dfd83e28fff02ea8ca351fc29a25a 100644
index 009150ece99c039c7e92ac89c8069a2e378db34a..d87b6ccc57be06739a0105805735feb1800ada69 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
@@ -3001,6 +3001,41 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
@@ -3005,6 +3005,41 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
public void showElderGuardian(boolean silent) {
if (getHandle().connection != null) getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, silent ? 0F : 1F));
}