diff --git a/paper-api/src/main/java/io/papermc/paper/command/brigadier/CommandSourceStack.java b/paper-api/src/main/java/io/papermc/paper/command/brigadier/CommandSourceStack.java index a6e01ffa3f30..1a6c78892a0e 100644 --- a/paper-api/src/main/java/io/papermc/paper/command/brigadier/CommandSourceStack.java +++ b/paper-api/src/main/java/io/papermc/paper/command/brigadier/CommandSourceStack.java @@ -2,9 +2,12 @@ import com.mojang.brigadier.RedirectModifier; import com.mojang.brigadier.tree.CommandNode; +import net.kyori.adventure.text.ComponentLike; +import org.bukkit.GameRule; import org.bukkit.Location; import org.bukkit.command.CommandSender; import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; import org.jetbrains.annotations.ApiStatus; import org.jspecify.annotations.Nullable; @@ -67,4 +70,34 @@ public interface CommandSourceStack { * @see com.mojang.brigadier.builder.ArgumentBuilder#fork(CommandNode, RedirectModifier) */ CommandSourceStack withExecutor(Entity executor); + + /** + * Sends a system message to the {@link #getExecutor()} if it is a {@link Player}, + * otherwise sends a system message to the {@link #getSender()}. + * + * @param message the message to send + */ + void sendToTarget(ComponentLike message); + + /** + * Sends a system message to the {@link #getSender()}, admins, and console indicating successful command execution + * according to vanilla semantics. + * + *

This currently includes checking for environments with suppressed output, + * {@link GameRule#SEND_COMMAND_FEEDBACK}, and {@link GameRule#LOG_ADMIN_COMMANDS}.

+ * + * @param message the message to send + * @param allowInformingAdmins whether admins and console may be informed of this success + */ + void sendSuccess(ComponentLike message, boolean allowInformingAdmins); + + /** + * Sends a system message indicating a failed command execution to the {@link #getSender()}. + * Does not apply red styling to the message as vanilla does to allow for custom failure message styling. + * + *

Respects vanilla semantics for accepting failure output and suppressed output environments.

+ * + * @param message the message to send + */ + void sendFailure(ComponentLike message); } diff --git a/paper-server/src/main/java/io/papermc/paper/command/brigadier/PaperCommandSourceStack.java b/paper-server/src/main/java/io/papermc/paper/command/brigadier/PaperCommandSourceStack.java index 0f7bbc9193bd..330919d7e50f 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/brigadier/PaperCommandSourceStack.java +++ b/paper-server/src/main/java/io/papermc/paper/command/brigadier/PaperCommandSourceStack.java @@ -2,6 +2,8 @@ import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource; import com.google.common.base.Preconditions; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.text.ComponentLike; import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; @@ -12,6 +14,8 @@ import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; +import static java.util.Objects.requireNonNull; + public interface PaperCommandSourceStack extends CommandSourceStack, BukkitBrigadierCommandSource { net.minecraft.commands.CommandSourceStack getHandle(); @@ -26,14 +30,12 @@ public interface PaperCommandSourceStack extends CommandSourceStack, BukkitBriga } @Override - @NonNull - default CommandSender getSender() { + default @NonNull CommandSender getSender() { return this.getHandle().getBukkitSender(); } @Override - @Nullable - default Entity getExecutor() { + default @Nullable Entity getExecutor() { net.minecraft.world.entity.Entity nmsEntity = this.getHandle().getEntity(); if (nmsEntity == null) { return null; @@ -43,11 +45,29 @@ default Entity getExecutor() { } @Override - default CommandSourceStack withExecutor(@NonNull Entity executor) { + default @NonNull CommandSourceStack withExecutor(@NonNull Entity executor) { Preconditions.checkNotNull(executor, "Executor cannot be null."); return this.getHandle().withEntity(((CraftEntity) executor).getHandle()); } + @Override + default void sendToTarget(final @NonNull ComponentLike message) { + requireNonNull(message, "message"); + this.getHandle().sendSystemMessage(PaperAdventure.asVanilla(message.asComponent())); + } + + @Override + default void sendSuccess(final @NonNull ComponentLike message, final boolean allowInformingAdmins) { + requireNonNull(message, "message"); + this.getHandle().sendSuccess(() -> PaperAdventure.asVanilla(message.asComponent()), allowInformingAdmins); + } + + @Override + default void sendFailure(final @NonNull ComponentLike message) { + requireNonNull(message, "message"); + this.getHandle().sendFailure(PaperAdventure.asVanilla(message.asComponent()), false); + } + // OLD METHODS @Override default org.bukkit.entity.Entity getBukkitEntity() { diff --git a/test-plugin/src/main/java/io/papermc/testplugin/brigtests/Registration.java b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/Registration.java index a45dcfc1512e..9572e2d6af36 100644 --- a/test-plugin/src/main/java/io/papermc/testplugin/brigtests/Registration.java +++ b/test-plugin/src/main/java/io/papermc/testplugin/brigtests/Registration.java @@ -1,6 +1,8 @@ package io.papermc.testplugin.brigtests; import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.BoolArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; import io.papermc.paper.command.brigadier.BasicCommand; import io.papermc.paper.command.brigadier.CommandSourceStack; import io.papermc.paper.command.brigadier.Commands; @@ -19,6 +21,8 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Material; import org.bukkit.command.CommandSender; import org.bukkit.command.defaults.BukkitCommand; @@ -45,7 +49,7 @@ private static void registerViaLifecycleEvents(final JavaPlugin plugin) { .then( Commands.argument("name", ArgumentTypes.resource(RegistryKey.ENCHANTMENT)) .executes(ctx -> { - ctx.getSource().getSender().sendPlainMessage(ctx.getArgument("name", Enchantment.class).toString()); + ctx.getSource().sendSuccess(Component.text(ctx.getArgument("name", Enchantment.class).toString()), false); return Command.SINGLE_SUCCESS; }) ).build() @@ -55,7 +59,7 @@ private static void registerViaLifecycleEvents(final JavaPlugin plugin) { Commands.argument("key", ArgumentTypes.resourceKey(RegistryKey.ENCHANTMENT)) .executes(ctx -> { final TypedKey key = RegistryArgumentExtractor.getTypedKey(ctx, RegistryKey.ENCHANTMENT, "key"); - ctx.getSource().getSender().sendPlainMessage(key.toString()); + ctx.getSource().sendSuccess(Component.text(key.toString()), false); return Command.SINGLE_SUCCESS; }) ).build() @@ -65,7 +69,7 @@ private static void registerViaLifecycleEvents(final JavaPlugin plugin) { Commands.argument("pos", ArgumentTypes.finePosition(false)) .executes(ctx -> { final FinePositionResolver position = ctx.getArgument("pos", FinePositionResolver.class); - ctx.getSource().getSender().sendPlainMessage("Position: " + position.resolve(ctx.getSource())); + ctx.getSource().sendSuccess(Component.text("Position: " + position.resolve(ctx.getSource())), false); return Command.SINGLE_SUCCESS; }) ).build() @@ -73,7 +77,7 @@ private static void registerViaLifecycleEvents(final JavaPlugin plugin) { // ensure plugin commands override commands.register(Commands.literal("tag") .executes(ctx -> { - ctx.getSource().getSender().sendPlainMessage("overriden command"); + ctx.getSource().sendSuccess(Component.text("overriden command"), false); return Command.SINGLE_SUCCESS; }) .build(), @@ -88,7 +92,7 @@ private static void registerViaLifecycleEvents(final JavaPlugin plugin) { .then(Commands.literal("sub_command") .requires(source -> source.getSender().hasPermission("testplugin.test")) .executes(ctx -> { - ctx.getSource().getSender().sendPlainMessage("root_command sub_command"); + ctx.getSource().sendSuccess(Component.text("root_command sub_command"), false); return Command.SINGLE_SUCCESS; })).build(), null, @@ -165,14 +169,14 @@ public static void registerViaBootstrap(final BootstrapContext context) { .then(Commands.literal("item") .then(Commands.argument("mat", MaterialArgumentType.item()) .executes(ctx -> { - ctx.getSource().getSender().sendPlainMessage(ctx.getArgument("mat", Material.class).name()); + ctx.getSource().sendSuccess(Component.text(ctx.getArgument("mat", Material.class).name()), false); return Command.SINGLE_SUCCESS; }) ) ).then(Commands.literal("block") .then(Commands.argument("mat", MaterialArgumentType.block()) .executes(ctx -> { - ctx.getSource().getSender().sendPlainMessage(ctx.getArgument("mat", Material.class).name()); + ctx.getSource().sendSuccess(Component.text(ctx.getArgument("mat", Material.class).name()), false); return Command.SINGLE_SUCCESS; }) ) @@ -181,6 +185,21 @@ public static void registerViaBootstrap(final BootstrapContext context) { null, Collections.emptyList() ); + + commands.register(Commands.literal("send_success") + .then(Commands.argument("allow_inform_admins", BoolArgumentType.bool()) + .then(Commands.argument("msg", StringArgumentType.greedyString()) + .executes(ctx -> { + ctx.getSource().sendSuccess( + MiniMessage.miniMessage().deserialize(StringArgumentType.getString(ctx, "msg")), + BoolArgumentType.getBool(ctx, "allow_inform_admins") + ); + return Command.SINGLE_SUCCESS; + }))) + .build(), + null, + Collections.emptyList() + ); }); lifecycleManager.registerEventHandler(LifecycleEvents.COMMANDS.newHandler(event -> { @@ -188,8 +207,8 @@ public static void registerViaBootstrap(final BootstrapContext context) { commands.register(Commands.literal("heya") .then(Commands.argument("range", ArgumentTypes.doubleRange()) .executes((ct) -> { - ct.getSource().getSender().sendPlainMessage(ct.getArgument("range", DoubleRangeProvider.class).range().toString()); - return 1; + ct.getSource().sendSuccess(Component.text(ct.getArgument("range", DoubleRangeProvider.class).range().toString()), false); + return Command.SINGLE_SUCCESS; }) ).build(), null,