From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Tue, 1 Feb 2022 15:51:55 -0700
Subject: [PATCH] API for creating command sender which forwards feedback


diff --git a/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java
new file mode 100644
index 0000000000000000000000000000000000000000..e3a5f1ec376319bdfda87fa27ae217bff3914292
--- /dev/null
+++ b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java
@@ -0,0 +1,111 @@
+package io.papermc.paper.commands;
+
+import io.papermc.paper.adventure.PaperAdventure;
+import java.util.UUID;
+import java.util.function.Consumer;
+import net.kyori.adventure.audience.MessageType;
+import net.kyori.adventure.identity.Identity;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+import net.minecraft.commands.CommandSource;
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.phys.Vec2;
+import net.minecraft.world.phys.Vec3;
+import org.bukkit.command.CommandSender;
+import org.bukkit.craftbukkit.CraftServer;
+import org.bukkit.craftbukkit.command.ServerCommandSender;
+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 FeedbackForwardingSender extends ServerCommandSender {
+    private final Consumer<? super Component> feedback;
+    private final CraftServer server;
+
+    public FeedbackForwardingSender(final Consumer<? super Component> feedback, final CraftServer server) {
+        super(((ServerCommandSender) server.getConsoleSender()).perm);
+        this.server = server;
+        this.feedback = feedback;
+    }
+
+    @Override
+    public void sendMessage(final String message) {
+        this.sendMessage(LegacyComponentSerializer.legacySection().deserialize(message));
+    }
+
+    @Override
+    public void sendMessage(final String... messages) {
+        for (final String message : messages) {
+            this.sendMessage(message);
+        }
+    }
+
+    @Override
+    public void sendMessage(final Identity identity, final Component message, final MessageType type) {
+        this.feedback.accept(message);
+    }
+
+    @Override
+    public String getName() {
+        return "FeedbackForwardingSender";
+    }
+
+    @Override
+    public Component name() {
+        return Component.text(this.getName());
+    }
+
+    @Override
+    public boolean isOp() {
+        return true;
+    }
+
+    @Override
+    public void setOp(final boolean value) {
+        throw new UnsupportedOperationException("Cannot change operator status of " + this.getClass().getName());
+    }
+
+    public CommandSourceStack asVanilla() {
+        final @Nullable ServerLevel overworld = this.server.getServer().overworld();
+        return new CommandSourceStack(
+            new Source(this),
+            overworld == null ? Vec3.ZERO : Vec3.atLowerCornerOf(overworld.getSharedSpawnPos()),
+            Vec2.ZERO,
+            overworld,
+            4,
+            this.getName(),
+            net.minecraft.network.chat.Component.literal(this.getName()),
+            this.server.getServer(),
+            null
+        );
+    }
+
+    private record Source(FeedbackForwardingSender sender) implements CommandSource {
+        @Override
+        public void sendSystemMessage(final net.minecraft.network.chat.Component message) {
+            this.sender.sendMessage(Identity.nil(), PaperAdventure.asAdventure(message));
+        }
+
+        @Override
+        public boolean acceptsSuccess() {
+            return true;
+        }
+
+        @Override
+        public boolean acceptsFailure() {
+            return true;
+        }
+
+        @Override
+        public boolean shouldInformAdmins() {
+            return false;
+        }
+
+        @Override
+        public CommandSender getBukkitSender(final CommandSourceStack stack) {
+            return this.sender;
+        }
+    }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 1fa90e90b1989aa9ce72beea6c15cd8fb1e24d06..82d3869afe0f4e9fd453d2e1cb179c4aabce9f14 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -2014,6 +2014,13 @@ public final class CraftServer implements Server {
         return console.console;
     }
 
+    // Paper start
+    @Override
+    public CommandSender createCommandSender(final java.util.function.Consumer<? super net.kyori.adventure.text.Component> feedback) {
+        return new io.papermc.paper.commands.FeedbackForwardingSender(feedback, this);
+    }
+    // Paper end
+
     public EntityMetadataStore getEntityMetadata() {
         return this.entityMetadata;
     }
diff --git a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java
index eaff8df6c8c12c64e005a68a02e2e35ed88f757c..1c638a4b1f2c841928d8b2a7ae43e4ebb1f7eac7 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java
@@ -13,9 +13,15 @@ import org.bukkit.plugin.Plugin;
 
 public abstract class ServerCommandSender implements CommandSender {
     private static PermissibleBase blockPermInst;
-    private final PermissibleBase perm;
+    public final PermissibleBase perm; // Paper
     private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers
 
+    // Paper start
+    public ServerCommandSender(final PermissibleBase permissibleBase) {
+        this.perm = permissibleBase;
+    }
+    // Paper end
+
     public ServerCommandSender() {
         if (this instanceof CraftBlockCommandSender) {
             if (ServerCommandSender.blockPermInst == null) {
diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
index 6df44aa60d2b41b95fe79ed4cf92a6d3369400ea..6035af2cf08353b3d3801220d8116d8611a0cd37 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
@@ -84,6 +84,11 @@ public final class VanillaCommandWrapper extends BukkitCommand {
         if (sender instanceof ProxiedCommandSender) {
             return ((ProxiedNativeCommandSender) sender).getHandle();
         }
+        // Paper start
+        if (sender instanceof io.papermc.paper.commands.FeedbackForwardingSender feedback) {
+            return feedback.asVanilla();
+        }
+        // Paper end
 
         throw new IllegalArgumentException("Cannot make " + sender + " a vanilla command listener");
     }