From 48d31a56e99cdc9d4ec9b748d8fd52038a508c94 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Tue, 22 Nov 2022 20:53:50 -0800 Subject: [PATCH] Add api to resolve components (#7648) --- patches/api/Adventure.patch | 66 +++++++++++++++++ patches/api/Timings-v2.patch | 2 +- patches/server/Adventure.patch | 71 ++++++++++++++++++- ...nt-suggestion-permissions-to-align-w.patch | 2 +- patches/server/MC-Dev-fixes.patch | 14 ++++ ...-Vanilla-Command-permission-checking.patch | 2 +- 6 files changed, 153 insertions(+), 4 deletions(-) diff --git a/patches/api/Adventure.patch b/patches/api/Adventure.patch index 5960fc458c..6ca30aca54 100644 --- a/patches/api/Adventure.patch +++ b/patches/api/Adventure.patch @@ -567,7 +567,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Bukkit; ++import org.bukkit.command.CommandSender; ++import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.io.IOException; + +/** + * Paper API-specific methods for working with {@link Component}s and related. @@ -578,6 +583,66 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + /** ++ * Resolves a component with a specific command sender and subject. ++ *

++ * Note that in Vanilla, elevated permissions are usually required to use ++ * '@' selectors in various component types, but this method should not ++ * check such permissions from the sender. ++ *

++ * A {@link CommandSender} argument is required to resolve: ++ *

++ * A {@link Entity} argument is optional to help resolve: ++ * ++ * {@link net.kyori.adventure.text.TranslatableComponent}s don't require any extra arguments. ++ * ++ * @param input the component to resolve ++ * @param context the command sender to resolve with ++ * @param scoreboardSubject the scoreboard subject to use (for use with {@link net.kyori.adventure.text.ScoreComponent}s) ++ * @return the resolved component ++ * @throws IOException if a syntax error tripped during resolving ++ */ ++ public static @NotNull Component resolveWithContext(@NotNull Component input, @Nullable CommandSender context, @Nullable Entity scoreboardSubject) throws IOException { ++ return resolveWithContext(input, context, scoreboardSubject, true); ++ } ++ ++ /** ++ * Resolves a component with a specific command sender and subject. ++ *

++ * Note that in Vanilla, elevated permissions are required to use ++ * '@' selectors in various component types. If the boolean {@code bypassPermissions} ++ * argument is {@code false}, the {@link CommandSender} argument will be used to query ++ * those permissions. ++ *

++ * A {@link CommandSender} argument is required to resolve: ++ *

++ * A {@link Entity} argument is optional to help resolve: ++ * ++ * {@link net.kyori.adventure.text.TranslatableComponent}s don't require any extra arguments. ++ * ++ * @param input the component to resolve ++ * @param context the command sender to resolve with ++ * @param scoreboardSubject the scoreboard subject to use (for use with {@link net.kyori.adventure.text.ScoreComponent}s) ++ * @param bypassPermissions true to bypass permissions checks for resolving components ++ * @return the resolved component ++ * @throws IOException if a syntax error tripped during resolving ++ */ ++ public static @NotNull Component resolveWithContext(@NotNull Component input, @Nullable CommandSender context, @Nullable Entity scoreboardSubject, boolean bypassPermissions) throws IOException { ++ return Bukkit.getUnsafe().resolveWithContext(input, context, scoreboardSubject, bypassPermissions); ++ } ++ ++ /** + * Return a component flattener that can use game data to resolve extra information about components. + * + * @return a component flattener @@ -1317,6 +1382,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + @Deprecated(forRemoval = true) net.kyori.adventure.text.serializer.gson.GsonComponentSerializer gsonComponentSerializer(); + @Deprecated(forRemoval = true) net.kyori.adventure.text.serializer.gson.GsonComponentSerializer colorDownsamplingGsonComponentSerializer(); + @Deprecated(forRemoval = true) net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer legacyComponentSerializer(); ++ net.kyori.adventure.text.Component resolveWithContext(net.kyori.adventure.text.Component component, org.bukkit.command.CommandSender context, org.bukkit.entity.Entity scoreboardSubject, boolean bypassPermissions) throws java.io.IOException; + // Paper end Material toLegacy(Material material); diff --git a/patches/api/Timings-v2.patch b/patches/api/Timings-v2.patch index 55a514b5ea..957431f78d 100644 --- a/patches/api/Timings-v2.patch +++ b/patches/api/Timings-v2.patch @@ -2838,7 +2838,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java @@ -0,0 +0,0 @@ public interface UnsafeValues { - @Deprecated(forRemoval = true) net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer legacyComponentSerializer(); + net.kyori.adventure.text.Component resolveWithContext(net.kyori.adventure.text.Component component, org.bukkit.command.CommandSender context, org.bukkit.entity.Entity scoreboardSubject, boolean bypassPermissions) throws java.io.IOException; // Paper end + void reportTimings(); // Paper diff --git a/patches/server/Adventure.patch b/patches/server/Adventure.patch index ef581ebaed..04c1559384 100644 --- a/patches/server/Adventure.patch +++ b/patches/server/Adventure.patch @@ -726,17 +726,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import net.kyori.adventure.translation.Translator; +import net.kyori.adventure.util.Codec; +import net.minecraft.ChatFormatting; ++import net.minecraft.commands.CommandSourceStack; +import net.minecraft.locale.Language; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.TagParser; ++import net.minecraft.network.chat.ComponentUtils; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.BossEvent; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.WrittenBookItem; -+import org.bukkit.ChatColor; ++import org.bukkit.command.CommandSender; ++import org.bukkit.craftbukkit.command.VanillaCommandWrapper; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + @@ -894,6 +899,24 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + ); + } + ++ public static Component resolveWithContext(final @NotNull Component component, final @Nullable CommandSender context, final @Nullable Entity scoreboardSubject, final boolean bypassPermissions) throws IOException { ++ final CommandSourceStack css = context != null ? VanillaCommandWrapper.getListener(context) : null; ++ Boolean previous = null; ++ if (css != null && bypassPermissions) { ++ previous = css.bypassSelectorPermissions; ++ css.bypassSelectorPermissions = true; ++ } ++ try { ++ return asAdventure(ComponentUtils.updateForEntity(css, asVanilla(component), scoreboardSubject == null ? null : ((CraftEntity) scoreboardSubject).getHandle(), 0)); ++ } catch (CommandSyntaxException e) { ++ throw new IOException(e); ++ } finally { ++ if (css != null && previous != null) { ++ css.bypassSelectorPermissions = previous; ++ } ++ } ++ } ++ + // BossBar + + public static BossEvent.BossBarColor asVanilla(final BossBar.Color color) { @@ -1423,6 +1446,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @Nullable public static ChatFormatting getById(int colorIndex) { if (colorIndex < 0) { +diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java ++++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java +@@ -0,0 +0,0 @@ public class CommandSourceStack implements SharedSuggestionProvider { + private final CommandSigningContext signingContext; + private final TaskChainer chatMessageChainer; + public volatile CommandNode currentCommand; // CraftBukkit ++ public boolean bypassSelectorPermissions = false; // Paper + + public CommandSourceStack(CommandSource output, Vec3 pos, Vec2 rot, ServerLevel world, int level, String name, Component displayName, MinecraftServer server, @Nullable Entity entity) { + this(output, pos, rot, world, level, name, displayName, server, entity, false, (commandcontext, flag, j) -> { diff --git a/src/main/java/net/minecraft/commands/arguments/MessageArgument.java b/src/main/java/net/minecraft/commands/arguments/MessageArgument.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/commands/arguments/MessageArgument.java @@ -1445,6 +1480,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 MessageArgument.logResolutionFailure(source, completableFuture); return completableFuture; } +diff --git a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java ++++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java +@@ -0,0 +0,0 @@ public class EntitySelector { + } + + private void checkPermissions(CommandSourceStack source) throws CommandSyntaxException { +- if (this.usesSelector && !source.hasPermission(2, "minecraft.command.selector")) { // CraftBukkit ++ if (source.bypassSelectorPermissions || (this.usesSelector && !source.hasPermission(2, "minecraft.command.selector"))) { // CraftBukkit // Paper + throw EntityArgument.ERROR_SELECTORS_NOT_ALLOWED.create(); + } + } diff --git a/src/main/java/net/minecraft/network/FriendlyByteBuf.java b/src/main/java/net/minecraft/network/FriendlyByteBuf.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/network/FriendlyByteBuf.java @@ -1693,6 +1741,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 JsonObject jsonobject = new JsonObject(); if (!ichatbasecomponent.getStyle().isEmpty()) { +diff --git a/src/main/java/net/minecraft/network/chat/ComponentUtils.java b/src/main/java/net/minecraft/network/chat/ComponentUtils.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/network/chat/ComponentUtils.java ++++ b/src/main/java/net/minecraft/network/chat/ComponentUtils.java +@@ -0,0 +0,0 @@ public class ComponentUtils { + if (depth > 100) { + return text.copy(); + } else { ++ // Paper start ++ if (text instanceof io.papermc.paper.adventure.AdventureComponent adventureComponent) { ++ text = adventureComponent.deepConverted(); ++ } ++ // Paper end + MutableComponent mutableComponent = text.getContents().resolve(source, sender, depth + 1); + + for(Component component : text.getSiblings()) { diff --git a/src/main/java/net/minecraft/network/chat/OutgoingPlayerChatMessage.java b/src/main/java/net/minecraft/network/chat/OutgoingPlayerChatMessage.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/network/chat/OutgoingPlayerChatMessage.java @@ -4432,6 +4496,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer legacyComponentSerializer() { + return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection(); + } ++ ++ @Override ++ public net.kyori.adventure.text.Component resolveWithContext(final net.kyori.adventure.text.Component component, final org.bukkit.command.CommandSender context, final org.bukkit.entity.Entity scoreboardSubject, final boolean bypassPermissions) throws IOException { ++ return io.papermc.paper.adventure.PaperAdventure.resolveWithContext(component, context, scoreboardSubject, bypassPermissions); ++ } + // Paper end + public static BlockState getBlock(MaterialData material) { diff --git a/patches/server/Fix-EntityArgument-suggestion-permissions-to-align-w.patch b/patches/server/Fix-EntityArgument-suggestion-permissions-to-align-w.patch index f0ee32fff5..057b09bf41 100644 --- a/patches/server/Fix-EntityArgument-suggestion-permissions-to-align-w.patch +++ b/patches/server/Fix-EntityArgument-suggestion-permissions-to-align-w.patch @@ -19,7 +19,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, icompletionprovider.hasPermission(2), true); // Paper + // Paper start + final boolean permission = object instanceof CommandSourceStack stack -+ ? stack.hasPermission(2, "minecraft.command.selector") ++ ? stack.bypassSelectorPermissions || stack.hasPermission(2, "minecraft.command.selector") + : icompletionprovider.hasPermission(2); + EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, permission, true); // Paper + // Paper end diff --git a/patches/server/MC-Dev-fixes.patch b/patches/server/MC-Dev-fixes.patch index 8ae926ca28..4226451200 100644 --- a/patches/server/MC-Dev-fixes.patch +++ b/patches/server/MC-Dev-fixes.patch @@ -128,6 +128,20 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private static final int MIN_PROTOCOL_ID = -1; private static final int MAX_PROTOCOL_ID = 2; private static final ConnectionProtocol[] LOOKUP = new ConnectionProtocol[4]; +diff --git a/src/main/java/net/minecraft/network/chat/ComponentUtils.java b/src/main/java/net/minecraft/network/chat/ComponentUtils.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/network/chat/ComponentUtils.java ++++ b/src/main/java/net/minecraft/network/chat/ComponentUtils.java +@@ -0,0 +0,0 @@ public class ComponentUtils { + ComponentContents string = text.getContents(); + if (string instanceof TranslatableContents) { + TranslatableContents translatableContents = (TranslatableContents)string; +- String string = translatableContents.getKey(); +- return Language.getInstance().has(string); ++ return Language.getInstance().has(translatableContents.getKey()); // Paper - decompile fix + } + } + diff --git a/src/main/java/net/minecraft/resources/RegistryLoader.java b/src/main/java/net/minecraft/resources/RegistryLoader.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/resources/RegistryLoader.java diff --git a/patches/server/Thread-Safe-Vanilla-Command-permission-checking.patch b/patches/server/Thread-Safe-Vanilla-Command-permission-checking.patch index 0f37478e4c..c6d2445035 100644 --- a/patches/server/Thread-Safe-Vanilla-Command-permission-checking.patch +++ b/patches/server/Thread-Safe-Vanilla-Command-permission-checking.patch @@ -35,9 +35,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private final TaskChainer chatMessageChainer; - public volatile CommandNode currentCommand; // CraftBukkit + public java.util.Map currentCommand = new java.util.concurrent.ConcurrentHashMap<>(); // CraftBukkit // Paper + public boolean bypassSelectorPermissions = false; // Paper public CommandSourceStack(CommandSource output, Vec3 pos, Vec2 rot, ServerLevel world, int level, String name, Component displayName, MinecraftServer server, @Nullable Entity entity) { - this(output, pos, rot, world, level, name, displayName, server, entity, false, (commandcontext, flag, j) -> { @@ -0,0 +0,0 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy @Override public boolean hasPermission(int level) {