Brigadier based command API

== AT ==
public net.minecraft.commands.arguments.blocks.BlockInput tag
public net.minecraft.commands.arguments.DimensionArgument ERROR_INVALID_VALUE
public net.minecraft.server.ReloadableServerResources registryLookup
public net.minecraft.server.ReloadableServerResources

Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
Co-authored-by: Marc Baloup <marc.baloup@laposte.net>
This commit is contained in:
Owen1212055 2022-08-01 22:50:34 -04:00
parent aabe9f5264
commit 977543c545
37 changed files with 2324 additions and 304 deletions

View file

@ -32,6 +32,24 @@
continue;
}
@@ -451,7 +459,7 @@
}
private String getSmartUsage(final CommandNode<S> node, final S source, final boolean optional, final boolean deep) {
- if (!node.canUse(source)) {
+ if (source != null && !node.canUse(source)) { // Paper
return null;
}
@@ -465,7 +473,7 @@
final String redirect = node.getRedirect() == this.root ? "..." : "-> " + node.getRedirect().getUsageText();
return self + CommandDispatcher.ARGUMENT_SEPARATOR + redirect;
} else {
- final Collection<CommandNode<S>> children = node.getChildren().stream().filter(c -> c.canUse(source)).collect(Collectors.toList());
+ final Collection<CommandNode<S>> children = node.getChildren().stream().filter(c -> source == null || c.canUse(source)).collect(Collectors.toList()); // Paper
if (children.size() == 1) {
final String usage = this.getSmartUsage(children.iterator().next(), source, childOptional, childOptional);
if (usage != null) {
@@ -537,10 +545,14 @@
int i = 0;
for (final CommandNode<S> node : parent.getChildren()) {

View file

@ -16,11 +16,13 @@
public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
private final Map<String, CommandNode<S>> children = new LinkedHashMap<>();
@@ -32,6 +34,14 @@
@@ -32,6 +34,16 @@
private final RedirectModifier<S> modifier;
private final boolean forks;
private Command<S> command;
+ public CommandNode<CommandSourceStack> clientNode; // Paper - Brigadier API
+ public CommandNode<io.papermc.paper.command.brigadier.CommandSourceStack> unwrappedCached = null; // Paper - Brigadier Command API
+ public CommandNode<io.papermc.paper.command.brigadier.CommandSourceStack> wrappedCached = null; // Paper - Brigadier Command API
+ // CraftBukkit start
+ public void removeCommand(String name) {
+ this.children.remove(name);
@ -31,7 +33,7 @@
protected CommandNode(final Command<S> command, final Predicate<S> requirement, final CommandNode<S> redirect, final RedirectModifier<S> modifier, final boolean forks) {
this.command = command;
@@ -61,7 +71,17 @@
@@ -61,7 +73,17 @@
return this.modifier;
}
@ -50,3 +52,15 @@
return this.requirement.test(source);
}
@@ -183,4 +205,11 @@
}
public abstract Collection<String> getExamples();
+ // Paper start - Brigadier Command API
+ public void clearAll() {
+ this.children.clear();
+ this.literals.clear();
+ this.arguments.clear();
+ }
+ // Paper end - Brigadier Command API
}

View file

@ -8,7 +8,7 @@
+ // CraftBukkit start
+ @Override
+ public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ return io.papermc.paper.brigadier.NullCommandSender.INSTANCE; // Paper - expose a no-op CommandSender
+ }
+ // CraftBukkit end
};

View file

@ -1,17 +1,18 @@
--- a/net/minecraft/commands/CommandSourceStack.java
+++ b/net/minecraft/commands/CommandSourceStack.java
@@ -45,8 +45,9 @@
@@ -45,9 +45,9 @@
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
+import com.mojang.brigadier.tree.CommandNode; // CraftBukkit
-public class CommandSourceStack implements ExecutionCommandSource<CommandSourceStack>, SharedSuggestionProvider {
+public class CommandSourceStack implements ExecutionCommandSource<CommandSourceStack>, SharedSuggestionProvider, com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource { // Paper - Brigadier API
-
+public class CommandSourceStack implements ExecutionCommandSource<CommandSourceStack>, SharedSuggestionProvider, io.papermc.paper.command.brigadier.PaperCommandSourceStack { // Paper - Brigadier API
public static final SimpleCommandExceptionType ERROR_NOT_PLAYER = new SimpleCommandExceptionType(Component.translatable("permissions.requires.player"));
public static final SimpleCommandExceptionType ERROR_NOT_ENTITY = new SimpleCommandExceptionType(Component.translatable("permissions.requires.entity"));
@@ -65,6 +66,8 @@
public final CommandSource source;
@@ -65,6 +65,8 @@
private final Vec2 rotation;
private final CommandSigningContext signingContext;
private final TaskChainer chatMessageChainer;
@ -20,31 +21,9 @@
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, CommandResultCallback.EMPTY, EntityAnchorArgument.Anchor.FEET, CommandSigningContext.ANONYMOUS, TaskChainer.immediate(server));
@@ -169,11 +172,66 @@
return this.textName;
}
@@ -171,8 +173,43 @@
+ // Paper start - Brigadier API
@Override
+ public org.bukkit.entity.Entity getBukkitEntity() {
+ return getEntity() != null ? getEntity().getBukkitEntity() : null;
+ }
+
+ @Override
+ public org.bukkit.World getBukkitWorld() {
+ return getLevel() != null ? getLevel().getWorld() : null;
+ }
+
+ @Override
+ public org.bukkit.Location getBukkitLocation() {
+ Vec3 pos = getPosition();
+ org.bukkit.World world = getBukkitWorld();
+ Vec2 rot = getRotation();
+ return world != null && pos != null ? new org.bukkit.Location(world, pos.x, pos.y, pos.z, rot != null ? rot.y : 0, rot != null ? rot.x : 0) : null;
+ }
+ // Paper end - Brigadier API
+
+ @Override
public boolean hasPermission(int level) {
+ // CraftBukkit start
+ // Paper start - Thread Safe Vanilla Command permission checking
@ -56,8 +35,8 @@
+ // CraftBukkit end
+
return this.permissionLevel >= level;
}
+ }
+
+ // Paper start - Fix permission levels for command blocks
+ private boolean forceRespectPermissionLevel() {
+ return this.source == CommandSource.NULL || (this.source instanceof final net.minecraft.world.level.BaseCommandBlock commandBlock && commandBlock.getLevel().paperConfig().commandBlocks.forceFollowPermLevel);
@ -81,13 +60,12 @@
+ }
+ return hasBukkitPerm.getAsBoolean();
+ // Paper end - Fix permission levels for command blocks
+ }
}
+ // CraftBukkit end
+
public Vec3 getPosition() {
return this.worldPosition;
}
@@ -302,21 +360,26 @@
@@ -302,21 +339,26 @@
while (iterator.hasNext()) {
ServerPlayer entityplayer = (ServerPlayer) iterator.next();
@ -117,11 +95,17 @@
}
}
@@ -400,4 +463,10 @@
@@ -400,4 +442,16 @@
public boolean isSilent() {
return this.silent;
}
+
+ // Paper start
+ @Override
+ public CommandSourceStack getHandle() {
+ return this;
+ }
+ // Paper end
+ // CraftBukkit start
+ public org.bukkit.command.CommandSender getBukkitSender() {
+ return this.source.getBukkitSender(this);

View file

@ -19,30 +19,45 @@
private final com.mojang.brigadier.CommandDispatcher<CommandSourceStack> dispatcher = new com.mojang.brigadier.CommandDispatcher();
public Commands(Commands.CommandSelection environment, CommandBuildContext commandRegistryAccess) {
+ this(); // CraftBukkit
+ // Paper
AdvancementCommands.register(this.dispatcher);
AttributeCommand.register(this.dispatcher, commandRegistryAccess);
ExecuteCommand.register(this.dispatcher, commandRegistryAccess);
@@ -252,6 +261,18 @@
PublishCommand.register(this.dispatcher);
}
@@ -250,8 +259,33 @@
if (environment.includeIntegrated) {
PublishCommand.register(this.dispatcher);
+ }
+
+ // Paper start - Vanilla command permission fixes
+ for (final CommandNode<CommandSourceStack> node : this.dispatcher.getRoot().getChildren()) {
+ if (node.getRequirement() == com.mojang.brigadier.builder.ArgumentBuilder.<CommandSourceStack>defaultRequirement()) {
+ node.requirement = stack -> stack.source == CommandSource.NULL || stack.getBukkitSender().hasPermission(org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(node));
+ }
+ }
}
+ // Paper end - Vanilla command permission fixes
+ // CraftBukkit start
+ }
+
+ public Commands() {
+ // CraftBukkkit end
+ // Paper start - Brigadier Command API
+ // Create legacy minecraft namespace commands
+ for (final CommandNode<CommandSourceStack> node : new java.util.ArrayList<>(this.dispatcher.getRoot().getChildren())) {
+ // The brigadier dispatcher is not able to resolve nested redirects.
+ // E.g. registering the alias minecraft:tp cannot redirect to tp, as tp itself redirects to teleport.
+ // Instead, target the first none redirecting node.
+ CommandNode<CommandSourceStack> flattenedAliasTarget = node;
+ while (flattenedAliasTarget.getRedirect() != null) flattenedAliasTarget = flattenedAliasTarget.getRedirect();
+ this.dispatcher.register(
+ com.mojang.brigadier.builder.LiteralArgumentBuilder.<CommandSourceStack>literal("minecraft:" + node.getName())
+ .executes(flattenedAliasTarget.getCommand())
+ .requires(flattenedAliasTarget.getRequirement())
+ .redirect(flattenedAliasTarget)
+ );
+ }
+ // Paper end - Brigadier Command API
+ // Paper - remove public constructor, no longer needed
this.dispatcher.setConsumer(ExecutionCommandSource.resultConsumer());
}
@@ -262,30 +283,69 @@
@@ -262,30 +296,75 @@
return new ParseResults(commandcontextbuilder1, parseResults.getReader(), parseResults.getExceptions());
}
@ -75,14 +90,14 @@
+
+ String newCommand = joiner.join(args);
+ this.performPrefixedCommand(sender, newCommand, newCommand);
}
+ }
+ // CraftBukkit end
+
+ public void performPrefixedCommand(CommandSourceStack source, String command) {
+ // CraftBukkit start
+ this.performPrefixedCommand(source, command, command);
+ }
+
}
+ public void performPrefixedCommand(CommandSourceStack commandlistenerwrapper, String s, String label) {
+ s = s.startsWith("/") ? s.substring(1) : s;
+ this.performCommand(this.dispatcher.parse(s, commandlistenerwrapper), s, label);
@ -95,6 +110,11 @@
+ }
+ public void performCommand(ParseResults<CommandSourceStack> parseresults, String s, String label) { // CraftBukkit
+ // Paper start
+ this.performCommand(parseresults, s, label, false);
+ }
+ public void performCommand(ParseResults<CommandSourceStack> parseresults, String s, String label, boolean throwCommandError) {
+ // Paper end
+ CommandSourceStack commandlistenerwrapper = (CommandSourceStack) parseresults.getContext().getSource();
+
Profiler.get().push(() -> {
@ -112,16 +132,17 @@
});
}
} catch (Exception exception) {
+ if (throwCommandError) throw exception;
MutableComponent ichatmutablecomponent = Component.literal(exception.getMessage() == null ? exception.getClass().getName() : exception.getMessage());
- if (Commands.LOGGER.isDebugEnabled()) {
- Commands.LOGGER.error("Command exception: /{}", command, exception);
+ Commands.LOGGER.error("Command exception: /{}", s, exception); // Paper - always show execution exception in console log
+ if (commandlistenerwrapper.getServer().isDebugging() || Commands.LOGGER.isDebugEnabled()) { // Paper - Debugging
+ Commands.LOGGER.error("Command exception: /{}", s, exception);
StackTraceElement[] astacktraceelement = exception.getStackTrace();
for (int i = 0; i < Math.min(astacktraceelement.length, 3); ++i) {
@@ -298,7 +358,7 @@
@@ -298,7 +377,7 @@
}));
if (SharedConstants.IS_RUNNING_IN_IDE) {
commandlistenerwrapper.sendFailure(Component.literal(Util.describeError(exception)));
@ -130,7 +151,7 @@
}
} finally {
Profiler.get().pop();
@@ -307,18 +367,22 @@
@@ -307,18 +386,22 @@
}
@Nullable
@ -159,7 +180,7 @@
});
if (i > 10) {
@@ -333,8 +397,18 @@
@@ -333,8 +416,18 @@
}
ichatmutablecomponent.append((Component) Component.translatable("command.context.here").withStyle(ChatFormatting.RED, ChatFormatting.ITALIC));
@ -179,7 +200,7 @@
return null;
}
@@ -368,7 +442,7 @@
@@ -368,7 +461,7 @@
executioncontext1.close();
} finally {
@ -188,7 +209,7 @@
}
} else {
callback.accept(executioncontext);
@@ -377,22 +451,89 @@
@@ -377,23 +470,121 @@
}
public void sendCommands(ServerPlayer player) {
@ -221,13 +242,7 @@
+ private void sendAsync(ServerPlayer player, Collection<CommandNode<CommandSourceStack>> dispatcherRootChildren) {
+ // Paper end - Perf: Async command map building
+ Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues
+ RootCommandNode vanillaRoot = new RootCommandNode();
+
+ RootCommandNode<CommandSourceStack> vanilla = player.server.vanillaCommandDispatcher.getDispatcher().getRoot();
+ map.put(vanilla, vanillaRoot);
+ this.fillUsableCommands(vanilla, vanillaRoot, player.createCommandSourceStack(), (Map) map);
+
+ // Now build the global commands in a second pass
+ // Paper - brigadier API removes the need to fill the map twice
RootCommandNode<SharedSuggestionProvider> rootcommandnode = new RootCommandNode();
map.put(this.dispatcher.getRoot(), rootcommandnode);
@ -265,6 +280,7 @@
- Iterator iterator = tree.getChildren().iterator();
+ // Paper start - Perf: Async command map building; pass copy of children
+ private void fillUsableCommands(Collection<CommandNode<CommandSourceStack>> children, CommandNode<SharedSuggestionProvider> result, CommandSourceStack source, Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> resultNodes) {
+ resultNodes.keySet().removeIf((node) -> !org.spigotmc.SpigotConfig.sendNamespaced && node.getName().contains( ":" )); // Paper - Remove namedspaced from result nodes to prevent redirect trimming ~ see comment below
+ Iterator iterator = children.iterator();
+ // Paper end - Perf: Async command map building
@ -280,10 +296,47 @@
if (commandnode2.canUse(source)) {
- ArgumentBuilder<SharedSuggestionProvider, ?> argumentbuilder = commandnode2.createBuilder();
+ ArgumentBuilder argumentbuilder = commandnode2.createBuilder(); // CraftBukkit - decompile error
+ // Paper start
+ /*
+ Because of how commands can be yeeted right left and center due to bad bukkit practices
+ we need to be able to ensure that ALL commands are registered (even redirects).
+ What this will do is IF the redirect seems to be "dead" it will create a builder and essentially populate (flatten)
+ all the children from the dead redirect to the node.
+
+ So, if minecraft:msg redirects to msg but the original msg node has been overriden minecraft:msg will now act as msg and will explicilty inherit its children.
+
+ The only way to fix this is to either:
+ - Send EVERYTHING flattened, don't use redirects
+ - Don't allow command nodes to be deleted
+ - Do this :)
+ */
+
+ // Is there an invalid command redirect?
+ if (argumentbuilder.getRedirect() != null && (CommandNode) resultNodes.get(argumentbuilder.getRedirect()) == null) {
+ // Create the argument builder with the same values as the specified node, but with a different literal and populated children
+
+ CommandNode<CommandSourceStack> redirect = argumentbuilder.getRedirect();
+ // Diff copied from LiteralCommand#createBuilder
+ final com.mojang.brigadier.builder.LiteralArgumentBuilder<CommandSourceStack> builder = com.mojang.brigadier.builder.LiteralArgumentBuilder.literal(commandnode2.getName());
+ builder.requires(redirect.getRequirement());
+ // builder.forward(redirect.getRedirect(), redirect.getRedirectModifier(), redirect.isFork()); We don't want to migrate the forward, since it's invalid.
+ if (redirect.getCommand() != null) {
+ builder.executes(redirect.getCommand());
+ }
+ // Diff copied from LiteralCommand#createBuilder
+ for (CommandNode<CommandSourceStack> child : redirect.getChildren()) {
+ builder.then(child);
+ }
+
+ argumentbuilder = builder;
+ }
+ // Paper end
+
argumentbuilder.requires((icompletionprovider) -> {
return true;
@@ -415,12 +556,12 @@
});
@@ -415,12 +606,12 @@
argumentbuilder.redirect((CommandNode) resultNodes.get(argumentbuilder.getRedirect()));
}
@ -298,7 +351,7 @@
}
}
}
@@ -481,7 +622,7 @@
@@ -481,7 +672,7 @@
}
private <T> HolderLookup.RegistryLookup.Delegate<T> createLookup(final HolderLookup.RegistryLookup<T> original) {

View file

@ -1,6 +1,18 @@
--- a/net/minecraft/commands/arguments/MessageArgument.java
+++ b/net/minecraft/commands/arguments/MessageArgument.java
@@ -54,17 +54,21 @@
@@ -40,6 +40,11 @@
public static void resolveChatMessage(CommandContext<CommandSourceStack> context, String name, Consumer<PlayerChatMessage> callback) throws CommandSyntaxException {
MessageArgument.Message message = context.getArgument(name, MessageArgument.Message.class);
+ // Paper start
+ resolveChatMessage(message, context, name, callback);
+ }
+ public static void resolveChatMessage(MessageArgument.Message message, CommandContext<CommandSourceStack> context, String name, Consumer<PlayerChatMessage> callback) throws CommandSyntaxException {
+ // Paper end
CommandSourceStack commandSourceStack = context.getSource();
Component component = message.resolveComponent(commandSourceStack);
CommandSigningContext commandSigningContext = commandSourceStack.getSigningContext();
@@ -54,17 +59,21 @@
private static void resolveSignedMessage(Consumer<PlayerChatMessage> callback, CommandSourceStack source, PlayerChatMessage message) {
MinecraftServer minecraftServer = source.getServer();
CompletableFuture<FilteredText> completableFuture = filterPlainText(source, message);

View file

@ -162,7 +162,7 @@
+ public static int currentTick; // Paper - improve tick loop
+ public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
+ public int autosavePeriod;
+ public Commands vanillaCommandDispatcher;
+ // Paper - don't store the vanilla dispatcher
+ private boolean forceTicks;
+ // CraftBukkit end
+ // Spigot start
@ -202,7 +202,7 @@
this.metricsRecorder = InactiveMetricsRecorder.INSTANCE;
this.onMetricsRecordingStopped = (methodprofilerresults) -> {
this.stopRecordingMetrics();
@@ -319,36 +373,68 @@
@@ -319,36 +373,67 @@
this.scoreboard = new ServerScoreboard(this);
this.customBossEvents = new CustomBossEvents();
this.suppressedExceptions = new SuppressedExceptionCollector();
@ -254,7 +254,6 @@
+ // CraftBukkit start
+ this.options = options;
+ this.worldLoader = worldLoader;
+ this.vanillaCommandDispatcher = worldstem.dataPackResources().commands; // CraftBukkit
+ // Paper start - Handled by TerminalConsoleAppender
+ // Try to see if we're actually running in a terminal, disable jline if not
+ /*
@ -286,7 +285,7 @@
}
private void readScoreboard(DimensionDataStorage persistentStateManager) {
@@ -357,7 +443,7 @@
@@ -357,7 +442,7 @@
protected abstract boolean initServer() throws IOException;
@ -295,7 +294,7 @@
if (!JvmProfiler.INSTANCE.isRunning()) {
;
}
@@ -365,12 +451,8 @@
@@ -365,12 +450,8 @@
boolean flag = false;
ProfiledDuration profiledduration = JvmProfiler.INSTANCE.onWorldLoadedStarted();
@ -309,7 +308,7 @@
if (profiledduration != null) {
profiledduration.finish(true);
}
@@ -387,23 +469,241 @@
@@ -387,23 +468,244 @@
protected void forceDifficulty() {}
@ -549,6 +548,9 @@
+
+ this.server.enablePlugins(org.bukkit.plugin.PluginLoadOrder.POSTWORLD);
+ if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins
+ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // Paper - reset invalid state for event fire below
+ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL); // Paper - call commands event for regular plugins
+ ((org.bukkit.craftbukkit.help.SimpleHelpMap) this.server.getHelpMap()).initializeCommands();
+ this.server.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.STARTUP));
+ this.connection.acceptConnections();
+ }
@ -565,7 +567,7 @@
if (!iworlddataserver.isInitialized()) {
try {
@@ -427,30 +727,8 @@
@@ -427,30 +729,8 @@
iworlddataserver.setInitialized(true);
}
@ -597,7 +599,7 @@
private static void setInitialSpawn(ServerLevel world, ServerLevelData worldProperties, boolean bonusChest, boolean debugWorld) {
if (debugWorld) {
@@ -458,6 +736,21 @@
@@ -458,6 +738,21 @@
} else {
ServerChunkCache chunkproviderserver = world.getChunkSource();
ChunkPos chunkcoordintpair = new ChunkPos(chunkproviderserver.randomState().sampler().findSpawnPosition());
@ -619,7 +621,7 @@
int i = chunkproviderserver.getGenerator().getSpawnHeight(world);
if (i < world.getMinY()) {
@@ -516,31 +809,36 @@
@@ -516,31 +811,36 @@
iworlddataserver.setGameType(GameType.SPECTATOR);
}
@ -667,7 +669,7 @@
ForcedChunksSavedData forcedchunk = (ForcedChunksSavedData) worldserver1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks");
if (forcedchunk != null) {
@@ -555,10 +853,17 @@
@@ -555,10 +855,17 @@
}
}
@ -689,7 +691,7 @@
}
public GameType getDefaultGameType() {
@@ -588,12 +893,16 @@
@@ -588,12 +895,16 @@
worldserver.save((ProgressListener) null, flush, worldserver.noSave && !force);
}
@ -708,7 +710,7 @@
if (flush) {
Iterator iterator1 = this.getAllLevels().iterator();
@@ -628,18 +937,45 @@
@@ -628,18 +939,45 @@
this.stopServer();
}
@ -755,7 +757,7 @@
}
MinecraftServer.LOGGER.info("Saving worlds");
@@ -693,6 +1029,15 @@
@@ -693,6 +1031,15 @@
} catch (IOException ioexception1) {
MinecraftServer.LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), ioexception1);
}
@ -771,7 +773,7 @@
}
@@ -709,6 +1054,14 @@
@@ -709,6 +1056,14 @@
}
public void halt(boolean waitForShutdown) {
@ -786,7 +788,7 @@
this.running = false;
if (waitForShutdown) {
try {
@@ -720,6 +1073,64 @@
@@ -720,6 +1075,64 @@
}
@ -851,14 +853,14 @@
protected void runServer() {
try {
if (!this.initServer()) {
@@ -727,9 +1138,26 @@
@@ -727,8 +1140,25 @@
}
this.nextTickTimeNanos = Util.getNanos();
- this.statusIcon = (ServerStatus.Favicon) this.loadStatusIcon().orElse((Object) null);
+ this.statusIcon = (ServerStatus.Favicon) this.loadStatusIcon().orElse(null); // CraftBukkit - decompile error
this.status = this.buildServerStatus();
+
+ // Spigot start
+ org.spigotmc.WatchdogThread.hasStarted = true; // Paper
+ Arrays.fill( this.recentTps, 20 );
@ -875,11 +877,10 @@
+ LOGGER.info("*************************************************************************************");
+ }
+ // Paper end - Add onboarding message for initial server start
+
while (this.running) {
long i;
@@ -744,12 +1172,31 @@
@@ -744,11 +1174,30 @@
if (j > MinecraftServer.OVERLOADED_THRESHOLD_NANOS + 20L * i && this.nextTickTimeNanos - this.lastOverloadWarningNanos >= MinecraftServer.OVERLOADED_WARNING_INTERVAL_NANOS + 100L * i) {
long k = j / i;
@ -888,7 +889,7 @@
this.nextTickTimeNanos += k * i;
this.lastOverloadWarningNanos = this.nextTickTimeNanos;
}
}
+ }
+ // Spigot start
+ // Paper start - further improve server tick loop
+ currentTime = Util.getNanos();
@ -898,20 +899,19 @@
+ tps1.add(currentTps, diff);
+ tps5.add(currentTps, diff);
+ tps15.add(currentTps, diff);
+
+ // Backwards compat with bad plugins
+ this.recentTps[0] = tps1.getAverage();
+ this.recentTps[1] = tps5.getAverage();
+ this.recentTps[2] = tps15.getAverage();
+ tickSection = currentTime;
+ }
}
+ // Paper end - further improve server tick loop
+ // Spigot end
+
boolean flag = i == 0L;
if (this.debugCommandProfilerDelayStart) {
@@ -757,6 +1204,8 @@
@@ -757,6 +1206,8 @@
this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount);
}
@ -920,7 +920,7 @@
this.nextTickTimeNanos += i;
try {
@@ -830,6 +1279,14 @@
@@ -830,6 +1281,14 @@
this.services.profileCache().clearExecutor();
}
@ -935,7 +935,7 @@
this.onServerExit();
}
@@ -889,9 +1346,16 @@
@@ -889,9 +1348,16 @@
}
private boolean haveTime() {
@ -953,7 +953,7 @@
public static boolean throwIfFatalException() {
RuntimeException runtimeexception = (RuntimeException) MinecraftServer.fatalException.get();
@@ -903,7 +1367,7 @@
@@ -903,7 +1369,7 @@
}
public static void setFatalException(RuntimeException exception) {
@ -962,7 +962,7 @@
}
@Override
@@ -961,6 +1425,7 @@
@@ -961,6 +1427,7 @@
if (super.pollTask()) {
return true;
} else {
@ -970,7 +970,7 @@
if (this.tickRateManager.isSprinting() || this.haveTime()) {
Iterator iterator = this.getAllLevels().iterator();
@@ -968,16 +1433,16 @@
@@ -968,16 +1435,16 @@
ServerLevel worldserver = (ServerLevel) iterator.next();
if (worldserver.getChunkSource().pollTask()) {
@ -990,7 +990,7 @@
Profiler.get().incrementCounter("runTask");
super.doRunTask(ticktask);
}
@@ -1025,6 +1490,7 @@
@@ -1025,6 +1492,7 @@
}
public void tickServer(BooleanSupplier shouldKeepTicking) {
@ -998,7 +998,7 @@
long i = Util.getNanos();
int j = this.pauseWhileEmptySeconds() * 20;
@@ -1041,11 +1507,13 @@
@@ -1041,11 +1509,13 @@
this.autoSave();
}
@ -1012,7 +1012,7 @@
++this.tickCount;
this.tickRateManager.tick();
this.tickChildren(shouldKeepTicking);
@@ -1055,12 +1523,18 @@
@@ -1055,12 +1525,18 @@
}
--this.ticksUntilAutosave;
@ -1032,7 +1032,7 @@
gameprofilerfiller.push("tallying");
long k = Util.getNanos() - i;
int l = this.tickCount % 100;
@@ -1069,12 +1543,17 @@
@@ -1069,12 +1545,17 @@
this.aggregatedTickTimesNanos += k;
this.tickTimesNanos[l] = k;
this.smoothedTickTimeMillis = this.smoothedTickTimeMillis * 0.8F + (float) k / (float) TimeUtil.NANOSECONDS_PER_MILLISECOND * 0.19999999F;
@ -1051,7 +1051,7 @@
MinecraftServer.LOGGER.debug("Autosave started");
ProfilerFiller gameprofilerfiller = Profiler.get();
@@ -1123,7 +1602,7 @@
@@ -1123,7 +1604,7 @@
private ServerStatus buildServerStatus() {
ServerStatus.Players serverping_serverpingplayersample = this.buildPlayerStatus();
@ -1060,7 +1060,7 @@
}
private ServerStatus.Players buildPlayerStatus() {
@@ -1133,7 +1612,7 @@
@@ -1133,7 +1614,7 @@
if (this.hidesOnlinePlayers()) {
return new ServerStatus.Players(i, list.size(), List.of());
} else {
@ -1069,7 +1069,7 @@
ObjectArrayList<GameProfile> objectarraylist = new ObjectArrayList(j);
int k = Mth.nextInt(this.random, 0, list.size() - j);
@@ -1154,24 +1633,72 @@
@@ -1154,24 +1635,72 @@
this.getPlayerList().getPlayers().forEach((entityplayer) -> {
entityplayer.connection.suspendFlushing();
});
@ -1100,7 +1100,7 @@
+ while (!this.processQueue.isEmpty()) {
+ this.processQueue.remove().run();
+ }
+
+ // Send time updates to everyone, it will get the right time from the world the player is in.
+ // Paper start - Perf: Optimize time updates
+ for (final ServerLevel level : this.getAllLevels()) {
@ -1120,7 +1120,7 @@
+ // Paper end - Perf: Optimize time updates
+ }
+ }
+
+ this.isIteratingOverLevels = true; // Paper - Throw exception on world create while being ticked
+ Iterator iterator = this.getAllLevels().iterator(); // Paper - Throw exception on world create while being ticked; move down
while (iterator.hasNext()) {
@ -1143,7 +1143,7 @@
gameprofilerfiller.push("tick");
@@ -1186,7 +1713,9 @@
@@ -1186,7 +1715,9 @@
gameprofilerfiller.pop();
gameprofilerfiller.pop();
@ -1153,7 +1153,7 @@
gameprofilerfiller.popPush("connection");
this.tickConnection();
@@ -1267,6 +1796,22 @@
@@ -1267,6 +1798,22 @@
return (ServerLevel) this.levels.get(key);
}
@ -1176,7 +1176,7 @@
public Set<ResourceKey<Level>> levelKeys() {
return this.levels.keySet();
}
@@ -1296,7 +1841,7 @@
@@ -1296,7 +1843,7 @@
@DontObfuscate
public String getServerModName() {
@ -1185,7 +1185,7 @@
}
public SystemReport fillSystemReport(SystemReport details) {
@@ -1347,7 +1892,7 @@
@@ -1347,7 +1894,7 @@
@Override
public void sendSystemMessage(Component message) {
@ -1194,7 +1194,7 @@
}
public KeyPair getKeyPair() {
@@ -1385,11 +1930,14 @@
@@ -1385,11 +1932,14 @@
}
}
@ -1214,7 +1214,7 @@
}
}
@@ -1403,7 +1951,7 @@
@@ -1403,7 +1953,7 @@
while (iterator.hasNext()) {
ServerLevel worldserver = (ServerLevel) iterator.next();
@ -1223,7 +1223,7 @@
}
}
@@ -1481,10 +2029,20 @@
@@ -1481,10 +2031,20 @@
@Override
public String getMotd() {
@ -1245,7 +1245,7 @@
this.motd = motd;
}
@@ -1507,7 +2065,7 @@
@@ -1507,7 +2067,7 @@
}
public ServerConnectionListener getConnection() {
@ -1254,7 +1254,7 @@
}
public boolean isReady() {
@@ -1593,7 +2151,7 @@
@@ -1593,7 +2153,7 @@
@Override
public void executeIfPossible(Runnable runnable) {
if (this.isStopped()) {
@ -1263,7 +1263,7 @@
} else {
super.executeIfPossible(runnable);
}
@@ -1632,13 +2190,19 @@
@@ -1632,13 +2192,19 @@
return this.functionManager;
}
@ -1285,14 +1285,15 @@
}, this).thenCompose((immutablelist) -> {
MultiPackResourceManager resourcemanager = new MultiPackResourceManager(PackType.SERVER_DATA, immutablelist);
List<Registry.PendingTags<?>> list = TagLoader.loadTagsForExistingRegistries(resourcemanager, this.registries.compositeAccess());
@@ -1654,17 +2218,21 @@
@@ -1652,6 +2218,7 @@
return new MinecraftServer.ReloadableResources(resourcemanager, datapackresources);
});
}).thenAcceptAsync((minecraftserver_reloadableresources) -> {
+ io.papermc.paper.command.brigadier.PaperBrigadier.moveBukkitCommands(this.resources.managers().getCommands(), minecraftserver_reloadableresources.managers().commands); // Paper
this.resources.close();
this.resources = minecraftserver_reloadableresources;
+ this.server.syncCommands(); // SPIGOT-5884: Lost on reload
this.packRepository.setSelected(dataPacks);
WorldDataConfiguration worlddataconfiguration = new WorldDataConfiguration(MinecraftServer.getSelectedPacks(this.packRepository, true), this.worldData.enabledFeatures());
@@ -1660,11 +2227,23 @@
this.worldData.setDataConfiguration(worlddataconfiguration);
this.resources.managers.updateStaticRegistryTags();
this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
@ -1303,11 +1304,20 @@
this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
this.fuelValues = FuelValues.vanillaBurnTimes(this.registries.compositeAccess(), this.worldData.enabledFeatures());
+ org.bukkit.craftbukkit.block.data.CraftBlockData.reloadCache(); // Paper - cache block data strings; they can be defined by datapacks so refresh it here
+ // Paper start - brigadier command API
+ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // reset invalid state for event fire below
+ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // call commands event for regular plugins
+ final org.bukkit.craftbukkit.help.SimpleHelpMap helpMap = (org.bukkit.craftbukkit.help.SimpleHelpMap) this.server.getHelpMap();
+ helpMap.clear();
+ helpMap.initializeGeneralTopics();
+ helpMap.initializeCommands();
+ this.server.syncCommands(); // Refresh commands after event
+ // Paper end
+ new io.papermc.paper.event.server.ServerResourcesReloadedEvent(cause).callEvent(); // Paper - Add ServerResourcesReloadedEvent; fire after everything has been reloaded
}, this);
if (this.isSameThread()) {
@@ -1789,14 +2357,15 @@
@@ -1789,14 +2368,15 @@
if (this.isEnforceWhitelist()) {
PlayerList playerlist = source.getServer().getPlayerList();
UserWhiteList whitelist = playerlist.getWhiteList();
@ -1325,7 +1335,7 @@
}
}
@@ -1952,7 +2521,7 @@
@@ -1952,7 +2532,7 @@
final List<String> list = Lists.newArrayList();
final GameRules gamerules = this.getGameRules();
@ -1334,7 +1344,7 @@
@Override
public <T extends GameRules.Value<T>> void visit(GameRules.Key<T> key, GameRules.Type<T> type) {
list.add(String.format(Locale.ROOT, "%s=%s\n", key.getId(), gamerules.getRule(key)));
@@ -2058,7 +2627,7 @@
@@ -2058,7 +2638,7 @@
try {
label51:
{
@ -1343,22 +1353,19 @@
try {
arraylist = Lists.newArrayList(NativeModuleLister.listModules());
@@ -2105,9 +2674,24 @@
if (bufferedwriter != null) {
bufferedwriter.close();
}
+
+ }
+
@@ -2108,6 +2688,21 @@
}
+ // CraftBukkit start
+ public boolean isDebugging() {
+ return false;
+ }
+
+ public static MinecraftServer getServer() {
+ return SERVER; // Paper
}
+ }
+
+ @Deprecated
+ public static RegistryAccess getDefaultRegistryAccess() {
+ return CraftRegistry.getMinecraftRegistry();
@ -1368,7 +1375,7 @@
private ProfilerFiller createProfiler() {
if (this.willStartRecordingMetrics) {
this.metricsRecorder = ActiveMetricsRecorder.createStarted(new ServerMetricsSamplersProvider(Util.timeSource, this.isDedicatedServer()), Util.timeSource, Util.ioPool(), new MetricsPersister("server"), this.onMetricsRecordingStopped, (path) -> {
@@ -2225,18 +2809,24 @@
@@ -2225,18 +2820,24 @@
}
public void logChatMessage(Component message, ChatType.Bound params, @Nullable String prefix) {
@ -1397,7 +1404,7 @@
}
public boolean logIPs() {
@@ -2379,4 +2969,30 @@
@@ -2379,4 +2980,30 @@
public static record ServerResourcePackInfo(UUID id, String url, String hash, boolean isRequired, @Nullable Component prompt) {
}

View file

@ -0,0 +1,25 @@
--- a/net/minecraft/server/ReloadableServerResources.java
+++ b/net/minecraft/server/ReloadableServerResources.java
@@ -39,6 +39,7 @@
this.postponedTags = pendingTagLoads;
this.recipes = new RecipeManager(registries);
this.commands = new Commands(environment, CommandBuildContext.simple(registries, enabledFeatures));
+ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setDispatcher(this.commands, CommandBuildContext.simple(registries, enabledFeatures)); // Paper - Brigadier Command API
this.advancements = new ServerAdvancementManager(registries);
this.functionLibrary = new ServerFunctionLibrary(functionPermissionLevel, this.commands.getDispatcher());
}
@@ -83,6 +84,14 @@
ReloadableServerResources reloadableServerResources = new ReloadableServerResources(
reloadResult.layers(), reloadResult.lookupWithUpdatedTags(), enabledFeatures, environment, pendingTagLoads, functionPermissionLevel
);
+ // Paper start - call commands event for bootstraps
+ //noinspection ConstantValue
+ io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(
+ io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS,
+ io.papermc.paper.command.brigadier.PaperCommands.INSTANCE,
+ io.papermc.paper.plugin.bootstrap.BootstrapContext.class,
+ MinecraftServer.getServer() == null ? io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.INITIAL : io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD);
+ // Paper end - call commands event
return SimpleReloadInstance.create(
resourceManager,
reloadableServerResources.listeners(),

View file

@ -5,7 +5,7 @@
public CommandDispatcher<CommandSourceStack> getDispatcher() {
- return this.server.getCommands().getDispatcher();
+ return this.server.vanillaCommandDispatcher.getDispatcher(); // CraftBukkit
+ return this.server.getCommands().getDispatcher(); // CraftBukkit // Paper - Don't override command dispatcher
}
public void tick() {

View file

@ -163,7 +163,7 @@
DedicatedServer.LOGGER.info("Loading properties");
DedicatedServerProperties dedicatedserverproperties = this.settings.getProperties();
@@ -126,14 +213,50 @@
@@ -126,14 +213,49 @@
this.setPreventProxyConnections(dedicatedserverproperties.preventProxyConnections);
this.setLocalIp(dedicatedserverproperties.serverIp);
}
@ -188,7 +188,6 @@
+ io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
+ com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
+ com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
+ io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // Paper - init PaperBrigadierProvider
this.setPvpAllowed(dedicatedserverproperties.pvp);
this.setFlightAllowed(dedicatedserverproperties.allowFlight);
@ -215,7 +214,7 @@
InetAddress inetaddress = null;
if (!this.getLocalIp().isEmpty()) {
@@ -143,34 +266,55 @@
@@ -143,34 +265,55 @@
if (this.getPort() < 0) {
this.setPort(dedicatedserverproperties.serverPort);
}
@ -277,7 +276,7 @@
this.debugSampleSubscriptionTracker = new DebugSampleSubscriptionTracker(this.getPlayerList());
this.tickTimeLogger = new RemoteSampleLogger(TpsDebugDimensions.values().length, this.debugSampleSubscriptionTracker, RemoteDebugSampleType.TICK_TIME);
long i = Util.getNanos();
@@ -178,13 +322,13 @@
@@ -178,13 +321,13 @@
SkullBlockEntity.setup(this.services, this);
GameProfileCache.setUsesAuthentication(this.usesAuthentication());
DedicatedServer.LOGGER.info("Preparing level \"{}\"", this.getLevelIdName());
@ -293,7 +292,7 @@
}
if (dedicatedserverproperties.enableQuery) {
@@ -197,7 +341,7 @@
@@ -197,7 +340,7 @@
this.rconThread = RconThread.create(this);
}
@ -302,7 +301,7 @@
Thread thread1 = new Thread(new ServerWatchdog(this));
thread1.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(DedicatedServer.LOGGER));
@@ -215,6 +359,12 @@
@@ -215,6 +358,12 @@
}
}
@ -315,7 +314,7 @@
@Override
public boolean isSpawningMonsters() {
return this.settings.getProperties().spawnMonsters && super.isSpawningMonsters();
@@ -227,7 +377,7 @@
@@ -227,7 +376,7 @@
@Override
public void forceDifficulty() {
@ -324,7 +323,7 @@
}
@Override
@@ -286,13 +436,14 @@
@@ -286,13 +435,14 @@
}
if (this.rconThread != null) {
@ -341,7 +340,7 @@
}
@Override
@@ -302,19 +453,29 @@
@@ -302,19 +452,29 @@
}
@Override
@ -377,7 +376,7 @@
}
}
@@ -383,7 +544,7 @@
@@ -383,7 +543,7 @@
@Override
public boolean isUnderSpawnProtection(ServerLevel world, BlockPos pos, Player player) {
@ -386,7 +385,7 @@
return false;
} else if (this.getPlayerList().getOps().isEmpty()) {
return false;
@@ -453,7 +614,11 @@
@@ -453,7 +613,11 @@
public boolean enforceSecureProfile() {
DedicatedServerProperties dedicatedserverproperties = this.getProperties();
@ -399,7 +398,7 @@
}
@Override
@@ -541,16 +706,52 @@
@@ -541,16 +705,52 @@
@Override
public String getPluginNames() {
@ -456,7 +455,7 @@
}
public void storeUsingWhiteList(boolean useWhitelist) {
@@ -660,4 +861,15 @@
@@ -660,4 +860,15 @@
}
}
}

View file

@ -1558,12 +1558,10 @@
}
return optional;
@@ -1564,8 +2394,129 @@
}
@@ -1566,6 +2396,117 @@
return false;
+ }
+
}
+ // CraftBukkit start - add method
+ public void chat(String s, PlayerChatMessage original, boolean async) {
+ if (s.isEmpty() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) {
@ -1656,39 +1654,29 @@
+ this.server.console.sendMessage(s);
+ }
+ }
}
+ private void handleCommand(String s) {
+ org.spigotmc.AsyncCatcher.catchOp("Command Dispatched Async: " + s); // Paper - Add async catcher
+ if ( org.spigotmc.SpigotConfig.logCommands ) // Spigot
+ this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s);
+ }
+
+ CraftPlayer player = this.getCraftPlayer();
+
+ PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(player, s, new LazyPlayerSet(this.server));
+ this.cserver.getPluginManager().callEvent(event);
+
+ if (event.isCancelled()) {
+ return;
+ }
+
+ try {
+ if (this.cserver.dispatchCommand(event.getPlayer(), event.getMessage().substring(1))) {
+ return;
+ }
+ } catch (org.bukkit.command.CommandException ex) {
+ player.sendMessage(org.bukkit.ChatColor.RED + "An internal error occurred while attempting to perform this command");
+ java.util.logging.Logger.getLogger(ServerGamePacketListenerImpl.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
+ return;
+ } finally {
+ @Deprecated // Paper
+ public void handleCommand(String s) { // Paper - private -> public
+ // Paper start - Remove all this old duplicated logic
+ if (s.startsWith("/")) {
+ s = s.substring(1);
+ }
+ /*
+ It should be noted that this represents the "legacy" command execution path.
+ Api can call commands even if there is no additional context provided.
+ This method should ONLY be used if you need to execute a command WITHOUT
+ an actual player's input.
+ */
+ this.performUnsignedChatCommand(s);
+ // Paper end
+ }
+ // CraftBukkit end
+
private PlayerChatMessage getSignedMessage(ServerboundChatPacket packet, LastSeenMessages lastSeenMessages) throws SignedMessageChain.DecodeException {
SignedMessageBody signedmessagebody = new SignedMessageBody(packet.message(), packet.timeStamp(), packet.salt(), lastSeenMessages);
@@ -1573,15 +2524,44 @@
@@ -1573,15 +2514,44 @@
}
private void broadcastChatMessage(PlayerChatMessage message) {
@ -1739,7 +1727,7 @@
}
@@ -1592,7 +2572,7 @@
@@ -1592,7 +2562,7 @@
synchronized (this.lastSeenMessages) {
if (!this.lastSeenMessages.applyOffset(packet.offset())) {
ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString());
@ -1748,7 +1736,7 @@
}
}
@@ -1601,7 +2581,40 @@
@@ -1601,7 +2571,40 @@
@Override
public void handleAnimate(ServerboundSwingPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@ -1789,7 +1777,7 @@
this.player.swing(packet.getHand());
}
@@ -1609,6 +2622,29 @@
@@ -1609,6 +2612,29 @@
public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
if (this.player.hasClientLoaded()) {
@ -1819,7 +1807,7 @@
this.player.resetLastActionTime();
Entity entity;
PlayerRideableJumping ijumpable;
@@ -1616,6 +2652,11 @@
@@ -1616,6 +2642,11 @@
switch (packet.getAction()) {
case PRESS_SHIFT_KEY:
this.player.setShiftKeyDown(true);
@ -1831,7 +1819,7 @@
break;
case RELEASE_SHIFT_KEY:
this.player.setShiftKeyDown(false);
@@ -1684,13 +2725,19 @@
@@ -1684,13 +2715,19 @@
}
if (i > 4096) {
@ -1852,7 +1840,7 @@
this.send(new ClientboundPlayerChatPacket(message.link().sender(), message.link().index(), message.signature(), message.signedBody().pack(this.messageSignatureCache), message.unsignedContent(), message.filterMask(), params));
this.addPendingMessage(message);
}
@@ -1701,7 +2748,19 @@
@@ -1701,7 +2738,19 @@
public SocketAddress getRemoteAddress() {
return this.connection.getRemoteAddress();
@ -1872,7 +1860,7 @@
public void switchToConfig() {
this.waitingForSwitchToConfig = true;
@@ -1718,9 +2777,17 @@
@@ -1718,9 +2767,17 @@
@Override
public void handleInteract(ServerboundInteractPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@ -1890,7 +1878,7 @@
this.player.resetLastActionTime();
this.player.setShiftKeyDown(packet.isUsingSecondaryAction());
@@ -1733,20 +2800,59 @@
@@ -1733,20 +2790,59 @@
if (this.player.canInteractWithEntity(axisalignedbb, 3.0D)) {
packet.dispatch(new ServerboundInteractPacket.Handler() {
@ -1954,7 +1942,7 @@
}
}
@@ -1755,19 +2861,20 @@
@@ -1755,19 +2851,20 @@
@Override
public void onInteraction(InteractionHand hand) {
@ -1978,7 +1966,7 @@
label23:
{
if (entity instanceof AbstractArrow) {
@@ -1785,17 +2892,41 @@
@@ -1785,17 +2882,41 @@
}
ServerGamePacketListenerImpl.this.player.attack(entity);
@ -2021,7 +2009,7 @@
}
}
@@ -1809,7 +2940,7 @@
@@ -1809,7 +2930,7 @@
case PERFORM_RESPAWN:
if (this.player.wonGame) {
this.player.wonGame = false;
@ -2030,7 +2018,7 @@
this.resetPosition();
CriteriaTriggers.CHANGED_DIMENSION.trigger(this.player, Level.END, Level.OVERWORLD);
} else {
@@ -1817,11 +2948,11 @@
@@ -1817,11 +2938,11 @@
return;
}
@ -2045,7 +2033,7 @@
}
}
break;
@@ -1833,16 +2964,27 @@
@@ -1833,16 +2954,27 @@
@Override
public void handleContainerClose(ServerboundContainerClosePacket packet) {
@ -2075,7 +2063,7 @@
this.player.containerMenu.sendAllDataToRemote();
} else if (!this.player.containerMenu.stillValid(this.player)) {
ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu);
@@ -1855,7 +2997,303 @@
@@ -1855,7 +2987,303 @@
boolean flag = packet.getStateId() != this.player.containerMenu.getStateId();
this.player.containerMenu.suppressRemoteUpdates();
@ -2380,7 +2368,7 @@
ObjectIterator objectiterator = Int2ObjectMaps.fastIterable(packet.getChangedSlots()).iterator();
while (objectiterator.hasNext()) {
@@ -1879,6 +3317,14 @@
@@ -1879,6 +3307,14 @@
@Override
public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) {
@ -2395,7 +2383,7 @@
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
this.player.resetLastActionTime();
if (!this.player.isSpectator() && this.player.containerMenu.containerId == packet.containerId()) {
@@ -1900,8 +3346,42 @@
@@ -1900,8 +3336,42 @@
ServerGamePacketListenerImpl.LOGGER.debug("Player {} tried to place impossible recipe {}", this.player, recipeholder.id().location());
return;
}
@ -2439,7 +2427,7 @@
if (containerrecipebook_a == RecipeBookMenu.PostPlaceAction.PLACE_GHOST_RECIPE) {
this.player.connection.send(new ClientboundPlaceGhostRecipePacket(this.player.containerMenu.containerId, craftingmanager_d.display().display()));
@@ -1917,6 +3397,7 @@
@@ -1917,6 +3387,7 @@
@Override
public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@ -2447,7 +2435,7 @@
this.player.resetLastActionTime();
if (this.player.containerMenu.containerId == packet.containerId() && !this.player.isSpectator()) {
if (!this.player.containerMenu.stillValid(this.player)) {
@@ -1945,7 +3426,44 @@
@@ -1945,7 +3416,44 @@
boolean flag1 = packet.slotNum() >= 1 && packet.slotNum() <= 45;
boolean flag2 = itemstack.isEmpty() || itemstack.getCount() <= itemstack.getMaxStackSize();
@ -2492,7 +2480,7 @@
if (flag1 && flag2) {
this.player.inventoryMenu.getSlot(packet.slotNum()).setByPlayer(itemstack);
this.player.inventoryMenu.setRemoteSlot(packet.slotNum(), itemstack);
@@ -1964,7 +3482,19 @@
@@ -1964,7 +3472,19 @@
@Override
public void handleSignUpdate(ServerboundSignUpdatePacket packet) {
@ -2513,7 +2501,7 @@
this.filterTextPacket(list).thenAcceptAsync((list1) -> {
this.updateSignText(packet, list1);
@@ -1972,6 +3502,7 @@
@@ -1972,6 +3492,7 @@
}
private void updateSignText(ServerboundSignUpdatePacket packet, List<FilteredText> signText) {
@ -2521,7 +2509,7 @@
this.player.resetLastActionTime();
ServerLevel worldserver = this.player.serverLevel();
BlockPos blockposition = packet.getPos();
@@ -1993,15 +3524,33 @@
@@ -1993,15 +3514,33 @@
@Override
public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
@ -2556,7 +2544,7 @@
if (this.player.isModelPartShown(PlayerModelPart.HAT) != flag) {
this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT, this.player));
}
@@ -2012,7 +3561,7 @@
@@ -2012,7 +3551,7 @@
public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) {
PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
if (this.player.hasPermissions(2) || this.isSingleplayerOwner()) {
@ -2565,7 +2553,7 @@
}
}
@@ -2033,7 +3582,7 @@
@@ -2033,7 +3572,7 @@
if (!Objects.equals(profilepublickey_a, profilepublickey_a1)) {
if (profilepublickey_a != null && profilepublickey_a1.expiresAt().isBefore(profilepublickey_a.expiresAt())) {
@ -2574,7 +2562,7 @@
} else {
try {
SignatureValidator signaturevalidator = this.server.getProfileKeySignatureValidator();
@@ -2045,8 +3594,8 @@
@@ -2045,8 +3584,8 @@
this.resetPlayerChatState(remotechatsession_a.validate(this.player.getGameProfile(), signaturevalidator));
} catch (ProfilePublicKey.ValidationException profilepublickey_b) {
@ -2585,7 +2573,7 @@
}
}
@@ -2058,7 +3607,7 @@
@@ -2058,7 +3597,7 @@
if (!this.waitingForSwitchToConfig) {
throw new IllegalStateException("Client acknowledged config, but none was requested");
} else {
@ -2594,7 +2582,7 @@
}
}
@@ -2076,15 +3625,18 @@
@@ -2076,15 +3615,18 @@
private void resetPlayerChatState(RemoteChatSession session) {
this.chatSession = session;
@ -2616,7 +2604,7 @@
@Override
public void handleClientTickEnd(ServerboundClientTickEndPacket packet) {
@@ -2115,4 +3667,17 @@
@@ -2115,4 +3657,17 @@
InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand);
}

View file

@ -0,0 +1,151 @@
package io.papermc.paper.brigadier;
import java.util.Set;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionAttachment;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@DefaultQualifier(NonNull.class)
public final class NullCommandSender implements CommandSender {
public static final CommandSender INSTANCE = new NullCommandSender();
private NullCommandSender() {
}
@Override
public void sendMessage(final String message) {
}
@Override
public void sendMessage(final String... messages) {
}
@Override
public void sendMessage(@Nullable final UUID sender, final String message) {
}
@Override
public void sendMessage(@Nullable final UUID sender, final String... messages) {
}
@SuppressWarnings("ConstantValue")
@Override
public Server getServer() {
final @Nullable Server server = Bukkit.getServer();
if (server == null) {
throw new UnsupportedOperationException("The server has not been created yet, you cannot access it at this time from the 'null' CommandSender");
}
return server;
}
@Override
public String getName() {
return "";
}
private final Spigot spigot = new Spigot();
@Override
public Spigot spigot() {
return this.spigot;
}
public final class Spigot extends CommandSender.Spigot {
@Override
public void sendMessage(@NotNull final BaseComponent component) {
}
@Override
public void sendMessage(@NonNull final @NotNull BaseComponent... components) {
}
@Override
public void sendMessage(@Nullable final UUID sender, @NotNull final BaseComponent component) {
}
@Override
public void sendMessage(@Nullable final UUID sender, @NonNull final @NotNull BaseComponent... components) {
}
}
@Override
public Component name() {
return Component.empty();
}
@Override
public boolean isPermissionSet(final String name) {
return false;
}
@Override
public boolean isPermissionSet(final Permission perm) {
return false;
}
@Override
public boolean hasPermission(final String name) {
return true;
}
@Override
public boolean hasPermission(final Permission perm) {
return true;
}
@Override
public PermissionAttachment addAttachment(final Plugin plugin, final String name, final boolean value) {
throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender");
}
@Override
public PermissionAttachment addAttachment(final Plugin plugin) {
throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender");
}
@Override
public @Nullable PermissionAttachment addAttachment(final Plugin plugin, final String name, final boolean value, final int ticks) {
throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender");
}
@Override
public @Nullable PermissionAttachment addAttachment(final Plugin plugin, final int ticks) {
throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender");
}
@Override
public void removeAttachment(final PermissionAttachment attachment) {
throw new UnsupportedOperationException("Cannot add attachments to the 'null' CommandSender");
}
@Override
public void recalculatePermissions() {
}
@Override
public Set<PermissionAttachmentInfo> getEffectivePermissions() {
throw new UnsupportedOperationException("Cannot remove attachments from the 'null' CommandSender");
}
@Override
public boolean isOp() {
return true;
}
@Override
public void setOp(final boolean value) {
}
}

View file

@ -1,30 +0,0 @@
package io.papermc.paper.brigadier;
import com.mojang.brigadier.Message;
import io.papermc.paper.adventure.PaperAdventure;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.minecraft.network.chat.ComponentUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import static java.util.Objects.requireNonNull;
public enum PaperBrigadierProviderImpl implements PaperBrigadierProvider {
INSTANCE;
PaperBrigadierProviderImpl() {
PaperBrigadierProvider.initialize(this);
}
@Override
public @NonNull Message message(final @NonNull ComponentLike componentLike) {
requireNonNull(componentLike, "componentLike");
return PaperAdventure.asVanilla(componentLike.asComponent());
}
@Override
public @NonNull Component componentFromMessage(final @NonNull Message message) {
requireNonNull(message, "message");
return PaperAdventure.asAdventure(ComponentUtils.fromMessage(message));
}
}

View file

@ -0,0 +1,253 @@
package io.papermc.paper.command.brigadier;
import com.google.common.collect.Collections2;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.LongArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
import io.papermc.paper.command.brigadier.argument.VanillaArgumentProviderImpl;
import io.papermc.paper.command.brigadier.argument.WrappedArgumentCommandNode;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Set;
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This root command node is responsible for wrapping around vanilla's dispatcher.
* <p>
* The reason for this is conversion is we do NOT want there to be NMS types
* in the api. This allows us to reconstruct the nodes to be more api friendly, while
* we can then unwrap it when needed and convert them to NMS types.
* <p>
* Command nodes such as vanilla (those without a proper "api node")
* will be assigned a {@link ShadowBrigNode}.
* This prevents certain parts of it (children) from being accessed by the api.
*/
public abstract class ApiMirrorRootNode extends RootCommandNode<CommandSourceStack> {
/**
* Represents argument types that are allowed to exist in the api.
* These typically represent primitives that don't need to be wrapped
* by NMS.
*/
private static final Set<Class<? extends ArgumentType<?>>> ARGUMENT_WHITELIST = Set.of(
BoolArgumentType.class,
DoubleArgumentType.class,
FloatArgumentType.class,
IntegerArgumentType.class,
LongArgumentType.class,
StringArgumentType.class
);
public static void validatePrimitiveType(ArgumentType<?> type) {
if (ARGUMENT_WHITELIST.contains(type.getClass())) {
if (!ArgumentTypeInfos.isClassRecognized(type.getClass())) {
throw new IllegalArgumentException("This whitelisted primitive argument type is not recognized by the server!");
}
} else if (!(type instanceof VanillaArgumentProviderImpl.NativeWrapperArgumentType<?,?> nativeWrapperArgumentType) || !ArgumentTypeInfos.isClassRecognized(nativeWrapperArgumentType.nativeNmsArgumentType().getClass())) {
throw new IllegalArgumentException("Custom argument type was passed, this was not a recognized type to send to the client! You must only pass vanilla arguments or primitive brig args in the wrapper!");
}
}
public abstract CommandDispatcher<net.minecraft.commands.CommandSourceStack> getDispatcher();
/**
* This logic is responsible for unwrapping an API node to be supported by NMS.
* See the method implementation for detailed steps.
*
* @param maybeWrappedNode api provided node / node to be "wrapped"
* @return wrapped node
*/
@SuppressWarnings({"rawtypes", "unchecked"})
private @NotNull CommandNode<CommandSourceStack> unwrapNode(final CommandNode<CommandSourceStack> maybeWrappedNode) {
/*
If the type is a shadow node we can assume that the type that it represents is an already supported NMS node.
This is because these are typically minecraft command nodes.
*/
if (maybeWrappedNode instanceof final ShadowBrigNode shadowBrigNode) {
return (CommandNode) shadowBrigNode.getHandle();
}
/*
This node already has had an unwrapped node created, so we can assume that it's safe to reuse that cached copy.
*/
if (maybeWrappedNode.unwrappedCached != null) {
return maybeWrappedNode.unwrappedCached;
}
// convert the pure brig node into one compatible with the nms dispatcher
return this.convertFromPureBrigNode(maybeWrappedNode);
}
private @NotNull CommandNode<CommandSourceStack> convertFromPureBrigNode(final CommandNode<CommandSourceStack> pureNode) {
/*
Logic for converting a node.
*/
final CommandNode<CommandSourceStack> converted;
if (pureNode instanceof final LiteralCommandNode<CommandSourceStack> node) {
/*
Remap the literal node, we only have to account
for the redirect in this case.
*/
converted = this.simpleUnwrap(node);
} else if (pureNode instanceof final ArgumentCommandNode<CommandSourceStack, ?> pureArgumentNode) {
final ArgumentType<?> pureArgumentType = pureArgumentNode.getType();
/*
Check to see if this argument type is a wrapped type, if so we know that
we can unwrap the node to get an NMS type.
*/
if (pureArgumentType instanceof final CustomArgumentType<?, ?> customArgumentType) {
final SuggestionProvider<?> suggestionProvider;
try {
final Method listSuggestions = customArgumentType.getClass().getMethod("listSuggestions", CommandContext.class, SuggestionsBuilder.class);
if (listSuggestions.getDeclaringClass() != CustomArgumentType.class) {
suggestionProvider = customArgumentType::listSuggestions;
} else {
suggestionProvider = null;
}
} catch (final NoSuchMethodException ex) {
throw new IllegalStateException("Could not determine if the custom argument type " + customArgumentType + " overrides listSuggestions", ex);
}
converted = this.unwrapArgumentWrapper(pureArgumentNode, customArgumentType, customArgumentType.getNativeType(), suggestionProvider);
} else if (pureArgumentType instanceof final VanillaArgumentProviderImpl.NativeWrapperArgumentType<?,?> nativeWrapperArgumentType) {
converted = this.unwrapArgumentWrapper(pureArgumentNode, nativeWrapperArgumentType, nativeWrapperArgumentType, null); // "null" for suggestion provider so it uses the argument type's suggestion provider
/*
If it's not a wrapped type, it either has to be a primitive or an already
defined NMS type.
This method allows us to check if this is recognized by vanilla.
*/
} else if (ArgumentTypeInfos.isClassRecognized(pureArgumentType.getClass())) {
// Allow any type of argument, as long as it's recognized by the client (but in most cases, this should be API only types)
// Previously we only allowed whitelisted types.
converted = this.simpleUnwrap(pureArgumentNode);
} else {
// Unknown argument type was passed
throw new IllegalArgumentException("Custom unknown argument type was passed, should be wrapped inside an CustomArgumentType.");
}
} else {
throw new IllegalArgumentException("Unknown command node passed. Don't know how to unwrap this.");
}
// Store unwrapped node before unwrapping children to avoid infinite recursion in cyclic redirects.
converted.wrappedCached = pureNode;
pureNode.unwrappedCached = converted;
/*
Add the children to the node, unwrapping each child in the process.
*/
for (final CommandNode<CommandSourceStack> child : pureNode.getChildren()) {
converted.addChild(this.unwrapNode(child));
}
return converted;
}
/**
* This logic is responsible for rewrapping a node.
* If a node was unwrapped in the past, it should have a wrapped type
* stored in its cache.
* <p>
* However, if it doesn't seem to have a wrapped version we will return
* a {@link ShadowBrigNode} instead. This supports being unwrapped/wrapped while
* preventing the API from accessing it unsafely.
*
* @param unwrapped argument node
* @return wrapped node
*/
private @Nullable CommandNode<CommandSourceStack> wrapNode(@Nullable final CommandNode<net.minecraft.commands.CommandSourceStack> unwrapped) {
if (unwrapped == null) {
return null;
}
/*
This was most likely created by API and has a wrapped variant,
so we can return this safely.
*/
if (unwrapped.wrappedCached != null) {
return unwrapped.wrappedCached;
}
/*
We don't know the type of this, or where this came from.
Return a shadow, where we will allow the api to handle this but have
restrictive access.
*/
CommandNode<CommandSourceStack> shadow = new ShadowBrigNode(unwrapped);
unwrapped.wrappedCached = shadow;
return shadow;
}
/**
* Nodes added to this dispatcher must be unwrapped
* in order to be added to the NMS dispatcher.
*
* @param node node to add
*/
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public void addChild(CommandNode<CommandSourceStack> node) {
CommandNode convertedNode = this.unwrapNode(node);
this.getDispatcher().getRoot().addChild(convertedNode);
}
/**
* Gets the children for the vanilla dispatcher,
* ensuring that all are wrapped.
*
* @return wrapped children
*/
@Override
public Collection<CommandNode<CommandSourceStack>> getChildren() {
return Collections2.transform(this.getDispatcher().getRoot().getChildren(), this::wrapNode);
}
@Override
public CommandNode<CommandSourceStack> getChild(String name) {
return this.wrapNode(this.getDispatcher().getRoot().getChild(name));
}
// These are needed for bukkit... we should NOT allow this
@Override
public void removeCommand(String name) {
this.getDispatcher().getRoot().removeCommand(name);
}
@Override
public void clearAll() {
this.getDispatcher().getRoot().clearAll();
}
@SuppressWarnings({"rawtypes", "unchecked"})
private CommandNode<CommandSourceStack> unwrapArgumentWrapper(final ArgumentCommandNode pureNode, final ArgumentType pureArgumentType, final ArgumentType possiblyWrappedNativeArgumentType, @Nullable SuggestionProvider argumentTypeSuggestionProvider) {
validatePrimitiveType(possiblyWrappedNativeArgumentType);
final CommandNode redirectNode = pureNode.getRedirect() == null ? null : this.unwrapNode(pureNode.getRedirect());
// If there is already a custom suggestion provider, ignore the suggestion provider from the argument type
final SuggestionProvider suggestionProvider = pureNode.getCustomSuggestions() != null ? pureNode.getCustomSuggestions() : argumentTypeSuggestionProvider;
final ArgumentType nativeArgumentType = possiblyWrappedNativeArgumentType instanceof final VanillaArgumentProviderImpl.NativeWrapperArgumentType<?,?> nativeWrapperArgumentType ? nativeWrapperArgumentType.nativeNmsArgumentType() : possiblyWrappedNativeArgumentType;
return new WrappedArgumentCommandNode<>(pureNode.getName(), pureArgumentType, nativeArgumentType, pureNode.getCommand(), pureNode.getRequirement(), redirectNode, pureNode.getRedirectModifier(), pureNode.isFork(), suggestionProvider);
}
private CommandNode<CommandSourceStack> simpleUnwrap(final CommandNode<CommandSourceStack> node) {
return node.createBuilder()
.forward(node.getRedirect() == null ? null : this.unwrapNode(node.getRedirect()), node.getRedirectModifier(), node.isFork())
.build();
}
}

View file

@ -0,0 +1,20 @@
package io.papermc.paper.command.brigadier;
import com.mojang.brigadier.Message;
import io.papermc.paper.adventure.PaperAdventure;
import net.kyori.adventure.text.Component;
import net.minecraft.network.chat.ComponentUtils;
import org.jetbrains.annotations.NotNull;
public final class MessageComponentSerializerImpl implements MessageComponentSerializer {
@Override
public @NotNull Component deserialize(@NotNull Message input) {
return PaperAdventure.asAdventure(ComponentUtils.fromMessage(input));
}
@Override
public @NotNull Message serialize(@NotNull Component component) {
return PaperAdventure.asVanilla(component);
}
}

View file

@ -0,0 +1,73 @@
package io.papermc.paper.command.brigadier;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import io.papermc.paper.command.brigadier.bukkit.BukkitBrigForwardingMap;
import io.papermc.paper.command.brigadier.bukkit.BukkitCommandNode;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
import java.util.Map;
public final class PaperBrigadier {
@SuppressWarnings("DataFlowIssue")
static final net.minecraft.commands.CommandSourceStack DUMMY = new net.minecraft.commands.CommandSourceStack(
CommandSource.NULL,
Vec3.ZERO,
Vec2.ZERO,
null,
4,
"",
CommonComponents.EMPTY,
null,
null
);
@SuppressWarnings({"unchecked", "rawtypes"})
public static Command wrapNode(CommandNode node) {
if (!(node instanceof LiteralCommandNode)) {
throw new IllegalArgumentException("Unsure how to wrap a " + node);
}
if (!(node instanceof PluginCommandNode pluginCommandNode)) {
return new VanillaCommandWrapper(null, node);
}
CommandNode<CommandSourceStack> argumentCommandNode = node;
if (argumentCommandNode.getRedirect() != null) {
argumentCommandNode = argumentCommandNode.getRedirect();
}
Map<CommandNode<CommandSourceStack>, String> map = PaperCommands.INSTANCE.getDispatcherInternal().getSmartUsage(argumentCommandNode, DUMMY);
String usage = map.isEmpty() ? pluginCommandNode.getUsageText() : pluginCommandNode.getUsageText() + " " + String.join("\n" + pluginCommandNode.getUsageText() + " ", map.values());
return new PluginVanillaCommandWrapper(pluginCommandNode.getName(), pluginCommandNode.getDescription(), usage, pluginCommandNode.getAliases(), node, pluginCommandNode.getPlugin());
}
/*
Previously, Bukkit used one command dispatcher and ignored minecraft's reloading logic.
In order to allow for legacy commands to be properly added, we will iterate through previous bukkit commands
in the old dispatcher and re-register them.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static void moveBukkitCommands(Commands before, Commands after) {
CommandDispatcher erasedDispatcher = before.getDispatcher();
for (Object node : erasedDispatcher.getRoot().getChildren()) {
if (node instanceof CommandNode<?> commandNode && commandNode.getCommand() instanceof BukkitCommandNode.BukkitBrigCommand) {
after.getDispatcher().getRoot().removeCommand(((CommandNode<?>) node).getName()); // Remove already existing commands
after.getDispatcher().getRoot().addChild((CommandNode<net.minecraft.commands.CommandSourceStack>) node);
}
}
}
}

View file

@ -0,0 +1,63 @@
package io.papermc.paper.command.brigadier;
import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface PaperCommandSourceStack extends CommandSourceStack, BukkitBrigadierCommandSource {
net.minecraft.commands.CommandSourceStack getHandle();
@Override
default @NotNull Location getLocation() {
Vec2 rot = this.getHandle().getRotation();
Vec3 pos = this.getHandle().getPosition();
Level level = this.getHandle().getLevel();
return new Location(level.getWorld(), pos.x, pos.y, pos.z, rot.y, rot.x);
}
@Override
@NotNull
default CommandSender getSender() {
return this.getHandle().getBukkitSender();
}
@Override
@Nullable
default Entity getExecutor() {
net.minecraft.world.entity.Entity nmsEntity = this.getHandle().getEntity();
if (nmsEntity == null) {
return null;
}
return nmsEntity.getBukkitEntity();
}
// OLD METHODS
@Override
default org.bukkit.entity.Entity getBukkitEntity() {
return this.getExecutor();
}
@Override
default org.bukkit.World getBukkitWorld() {
return this.getLocation().getWorld();
}
@Override
default org.bukkit.Location getBukkitLocation() {
return this.getLocation();
}
@Override
default CommandSender getBukkitSender() {
return this.getSender();
}
}

View file

@ -0,0 +1,204 @@
package io.papermc.paper.command.brigadier;
import com.google.common.base.Preconditions;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import io.papermc.paper.command.brigadier.bukkit.BukkitCommandNode;
import io.papermc.paper.plugin.configuration.PluginMeta;
import io.papermc.paper.plugin.lifecycle.event.LifecycleEventOwner;
import io.papermc.paper.plugin.lifecycle.event.registrar.PaperRegistrar;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import net.minecraft.commands.CommandBuildContext;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
import static java.util.Objects.requireNonNull;
@DefaultQualifier(NonNull.class)
public class PaperCommands implements Commands, PaperRegistrar<LifecycleEventOwner> {
public static final PaperCommands INSTANCE = new PaperCommands();
private @Nullable LifecycleEventOwner currentContext;
private @MonotonicNonNull CommandDispatcher<CommandSourceStack> dispatcher;
private @MonotonicNonNull CommandBuildContext buildContext;
private boolean invalid = false;
@Override
public void setCurrentContext(final @Nullable LifecycleEventOwner context) {
this.currentContext = context;
}
public void setDispatcher(final net.minecraft.commands.Commands commands, final CommandBuildContext commandBuildContext) {
this.invalid = false;
this.dispatcher = new CommandDispatcher<>(new ApiMirrorRootNode() {
@Override
public CommandDispatcher<net.minecraft.commands.CommandSourceStack> getDispatcher() {
return commands.getDispatcher();
}
});
this.buildContext = commandBuildContext;
}
public void setValid() {
this.invalid = false;
}
@Override
public void invalidate() {
this.invalid = true;
}
// use this method internally as it bypasses the valid check
public CommandDispatcher<CommandSourceStack> getDispatcherInternal() {
Preconditions.checkState(this.dispatcher != null, "the dispatcher hasn't been set yet");
return this.dispatcher;
}
public CommandBuildContext getBuildContext() {
Preconditions.checkState(this.buildContext != null, "the build context hasn't been set yet");
return this.buildContext;
}
@Override
public CommandDispatcher<CommandSourceStack> getDispatcher() {
Preconditions.checkState(!this.invalid && this.dispatcher != null, "cannot access the dispatcher in this context");
return this.dispatcher;
}
@Override
public @Unmodifiable Set<String> register(final LiteralCommandNode<CommandSourceStack> node, final @Nullable String description, final Collection<String> aliases) {
return this.register(requireNonNull(this.currentContext, "No lifecycle owner context is set").getPluginMeta(), node, description, aliases);
}
@Override
public @Unmodifiable Set<String> register(final PluginMeta pluginMeta, final LiteralCommandNode<CommandSourceStack> node, final @Nullable String description, final Collection<String> aliases) {
return this.registerWithFlags(pluginMeta, node, description, aliases, Set.of());
}
@Override
public @Unmodifiable Set<String> registerWithFlags(@NotNull final PluginMeta pluginMeta, @NotNull final LiteralCommandNode<CommandSourceStack> node, @org.jetbrains.annotations.Nullable final String description, @NotNull final Collection<String> aliases, @NotNull final Set<CommandRegistrationFlag> flags) {
final boolean hasFlattenRedirectFlag = flags.contains(CommandRegistrationFlag.FLATTEN_ALIASES);
final String identifier = pluginMeta.getName().toLowerCase(Locale.ROOT);
final String literal = node.getLiteral();
final PluginCommandNode pluginLiteral = new PluginCommandNode(identifier + ":" + literal, pluginMeta, node, description); // Treat the keyed version of the command as the root
final Set<String> registeredLabels = new HashSet<>(aliases.size() * 2 + 2);
if (this.registerIntoDispatcher(pluginLiteral, true)) {
registeredLabels.add(pluginLiteral.getLiteral());
}
if (this.registerRedirect(literal, pluginMeta, pluginLiteral, description, true, hasFlattenRedirectFlag)) { // Plugin commands should override vanilla commands
registeredLabels.add(literal);
}
// Add aliases
final List<String> registeredAliases = new ArrayList<>(aliases.size() * 2);
for (final String alias : aliases) {
if (this.registerRedirect(alias, pluginMeta, pluginLiteral, description, false, hasFlattenRedirectFlag)) {
registeredAliases.add(alias);
}
if (this.registerRedirect(identifier + ":" + alias, pluginMeta, pluginLiteral, description, false, hasFlattenRedirectFlag)) {
registeredAliases.add(identifier + ":" + alias);
}
}
if (!registeredAliases.isEmpty()) {
pluginLiteral.setAliases(registeredAliases);
}
registeredLabels.addAll(registeredAliases);
return registeredLabels.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(registeredLabels);
}
private boolean registerRedirect(final String aliasLiteral, final PluginMeta plugin, final PluginCommandNode redirectTo, final @Nullable String description, final boolean override, boolean hasFlattenRedirectFlag) {
final LiteralCommandNode<CommandSourceStack> redirect;
if (redirectTo.getChildren().isEmpty() || hasFlattenRedirectFlag) {
redirect = Commands.literal(aliasLiteral)
.executes(redirectTo.getCommand())
.requires(redirectTo.getRequirement())
.build();
for (final CommandNode<CommandSourceStack> child : redirectTo.getChildren()) {
redirect.addChild(child);
}
} else {
redirect = Commands.literal(aliasLiteral)
.executes(redirectTo.getCommand())
.redirect(redirectTo)
.requires(redirectTo.getRequirement())
.build();
}
return this.registerIntoDispatcher(new PluginCommandNode(aliasLiteral, plugin, redirect, description), override);
}
private boolean registerIntoDispatcher(final PluginCommandNode node, boolean override) {
final @Nullable CommandNode<CommandSourceStack> existingChild = this.getDispatcher().getRoot().getChild(node.getLiteral());
if (existingChild != null && !(existingChild instanceof PluginCommandNode) && !(existingChild instanceof BukkitCommandNode)) {
override = true; // override vanilla commands
}
if (existingChild == null || override) { // Avoid merging behavior. Maybe something to look into in the future
if (override) {
this.getDispatcher().getRoot().removeCommand(node.getLiteral());
}
this.getDispatcher().getRoot().addChild(node);
return true;
}
return false;
}
@Override
public @Unmodifiable Set<String> register(final String label, final @Nullable String description, final Collection<String> aliases, final BasicCommand basicCommand) {
return this.register(requireNonNull(this.currentContext, "No lifecycle owner context is set").getPluginMeta(), label, description, aliases, basicCommand);
}
@Override
public @Unmodifiable Set<String> register(final PluginMeta pluginMeta, final String label, final @Nullable String description, final Collection<String> aliases, final BasicCommand basicCommand) {
final LiteralArgumentBuilder<CommandSourceStack> builder = Commands.literal(label)
.requires(stack -> basicCommand.canUse(stack.getSender()))
.then(
Commands.argument("args", StringArgumentType.greedyString())
.suggests((context, suggestionsBuilder) -> {
String[] args = StringUtils.split(suggestionsBuilder.getRemaining());
if (suggestionsBuilder.getRemaining().endsWith(" ")) {
// if there is trailing whitespace, we should add an empty argument to signify
// that there may be more, but no characters have been typed yet
args = ArrayUtils.add(args, "");
}
final SuggestionsBuilder offsetSuggestionsBuilder = suggestionsBuilder.createOffset(suggestionsBuilder.getInput().lastIndexOf(' ') + 1);
final Collection<String> suggestions = basicCommand.suggest(context.getSource(), args);
suggestions.forEach(offsetSuggestionsBuilder::suggest);
return offsetSuggestionsBuilder.buildFuture();
})
.executes((stack) -> {
basicCommand.execute(stack.getSource(), StringUtils.split(stack.getArgument("args", String.class), ' '));
return com.mojang.brigadier.Command.SINGLE_SUCCESS;
})
)
.executes((stack) -> {
basicCommand.execute(stack.getSource(), new String[0]);
return com.mojang.brigadier.Command.SINGLE_SUCCESS;
});
return this.register(pluginMeta, builder.build(), description, aliases);
}
}

View file

@ -0,0 +1,50 @@
package io.papermc.paper.command.brigadier;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import io.papermc.paper.plugin.configuration.PluginMeta;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PluginCommandNode extends LiteralCommandNode<CommandSourceStack> {
private final PluginMeta plugin;
private final String description;
private List<String> aliases = Collections.emptyList();
public PluginCommandNode(final @NotNull String literal, final @NotNull PluginMeta plugin, final @NotNull LiteralCommandNode<CommandSourceStack> rootLiteral, final @Nullable String description) {
super(
literal, rootLiteral.getCommand(), rootLiteral.getRequirement(),
rootLiteral.getRedirect(), rootLiteral.getRedirectModifier(), rootLiteral.isFork()
);
this.plugin = plugin;
this.description = description;
for (CommandNode<CommandSourceStack> argument : rootLiteral.getChildren()) {
this.addChild(argument);
}
}
@NotNull
public Plugin getPlugin() {
return Objects.requireNonNull(Bukkit.getPluginManager().getPlugin(this.plugin.getName()));
}
@NotNull
public String getDescription() {
return this.description;
}
public void setAliases(List<String> aliases) {
this.aliases = aliases;
}
public List<String> getAliases() {
return this.aliases;
}
}

View file

@ -0,0 +1,46 @@
package io.papermc.paper.command.brigadier;
import com.mojang.brigadier.tree.CommandNode;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import org.bukkit.command.Command;
import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.util.List;
// Exists to that /help can show the plugin
public class PluginVanillaCommandWrapper extends VanillaCommandWrapper implements PluginIdentifiableCommand {
private final Plugin plugin;
private final List<String> alises;
public PluginVanillaCommandWrapper(String name, String description, String usageMessage, List<String> aliases, CommandNode<CommandSourceStack> vanillaCommand, Plugin plugin) {
super(name, description, usageMessage, aliases, vanillaCommand);
this.plugin = plugin;
this.alises = aliases;
}
@Override
public @NotNull List<String> getAliases() {
return this.alises;
}
@Override
public @NotNull Command setAliases(@NotNull List<String> aliases) {
return this;
}
@Override
public @NotNull Plugin getPlugin() {
return this.plugin;
}
// Show in help menu!
@Override
public boolean isRegistered() {
return true;
}
}

View file

@ -0,0 +1,35 @@
package io.papermc.paper.command.brigadier;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import java.util.Collection;
public class ShadowBrigNode extends LiteralCommandNode<CommandSourceStack> {
private final CommandNode<net.minecraft.commands.CommandSourceStack> handle;
public ShadowBrigNode(CommandNode<net.minecraft.commands.CommandSourceStack> node) {
super(node.getName(), context -> 0, (s) -> false, node.getRedirect() == null ? null : new ShadowBrigNode(node.getRedirect()), null, node.isFork());
this.handle = node;
}
@Override
public Collection<CommandNode<CommandSourceStack>> getChildren() {
throw new UnsupportedOperationException("Cannot retrieve children from this node.");
}
@Override
public CommandNode<CommandSourceStack> getChild(String name) {
throw new UnsupportedOperationException("Cannot retrieve children from this node.");
}
@Override
public void addChild(CommandNode<CommandSourceStack> node) {
throw new UnsupportedOperationException("Cannot modify children for this node.");
}
public CommandNode<net.minecraft.commands.CommandSourceStack> getHandle() {
return this.handle;
}
}

View file

@ -0,0 +1,30 @@
package io.papermc.paper.command.brigadier.argument;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.chat.SignedMessage;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.MessageArgument;
import org.jspecify.annotations.NullMarked;
@NullMarked
public record SignedMessageResolverImpl(MessageArgument.Message message) implements SignedMessageResolver {
@Override
public String content() {
return this.message.text();
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public CompletableFuture<SignedMessage> resolveSignedMessage(final String argumentName, final CommandContext erased) throws CommandSyntaxException {
final CompletableFuture<SignedMessage> future = new CompletableFuture<>();
final MessageArgument.Message response = ((CommandContext<CommandSourceStack>) erased).getArgument(argumentName, SignedMessageResolverImpl.class).message;
MessageArgument.resolveChatMessage(response, erased, argumentName, (message) -> {
future.complete(message.adventureView());
});
return future;
}
}

View file

@ -0,0 +1,366 @@
package io.papermc.paper.command.brigadier.argument;
import com.destroystokyo.paper.profile.CraftPlayerProfile;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.collect.Range;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import io.papermc.paper.adventure.PaperAdventure;
import io.papermc.paper.command.brigadier.PaperCommands;
import io.papermc.paper.command.brigadier.argument.predicate.ItemStackPredicate;
import io.papermc.paper.command.brigadier.argument.range.DoubleRangeProvider;
import io.papermc.paper.command.brigadier.argument.range.IntegerRangeProvider;
import io.papermc.paper.command.brigadier.argument.range.RangeProvider;
import io.papermc.paper.command.brigadier.argument.resolvers.BlockPositionResolver;
import io.papermc.paper.command.brigadier.argument.resolvers.FinePositionResolver;
import io.papermc.paper.command.brigadier.argument.resolvers.PlayerProfileListResolver;
import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver;
import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver;
import io.papermc.paper.entity.LookAnchor;
import io.papermc.paper.registry.PaperRegistries;
import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.util.MCUtil;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import net.minecraft.advancements.critereon.MinMaxBounds;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.ColorArgument;
import net.minecraft.commands.arguments.ComponentArgument;
import net.minecraft.commands.arguments.DimensionArgument;
import net.minecraft.commands.arguments.EntityAnchorArgument;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.commands.arguments.GameModeArgument;
import net.minecraft.commands.arguments.GameProfileArgument;
import net.minecraft.commands.arguments.HeightmapTypeArgument;
import net.minecraft.commands.arguments.MessageArgument;
import net.minecraft.commands.arguments.ObjectiveCriteriaArgument;
import net.minecraft.commands.arguments.RangeArgument;
import net.minecraft.commands.arguments.ResourceArgument;
import net.minecraft.commands.arguments.ResourceKeyArgument;
import net.minecraft.commands.arguments.ResourceLocationArgument;
import net.minecraft.commands.arguments.ScoreboardSlotArgument;
import net.minecraft.commands.arguments.StyleArgument;
import net.minecraft.commands.arguments.TemplateMirrorArgument;
import net.minecraft.commands.arguments.TemplateRotationArgument;
import net.minecraft.commands.arguments.TimeArgument;
import net.minecraft.commands.arguments.UuidArgument;
import net.minecraft.commands.arguments.blocks.BlockStateArgument;
import net.minecraft.commands.arguments.coordinates.BlockPosArgument;
import net.minecraft.commands.arguments.coordinates.Vec3Argument;
import net.minecraft.commands.arguments.item.ItemArgument;
import net.minecraft.commands.arguments.item.ItemPredicateArgument;
import net.minecraft.core.BlockPos;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import org.bukkit.GameMode;
import org.bukkit.HeightMap;
import org.bukkit.Keyed;
import org.bukkit.NamespacedKey;
import org.bukkit.World;
import org.bukkit.block.BlockState;
import org.bukkit.block.structure.Mirror;
import org.bukkit.block.structure.StructureRotation;
import org.bukkit.craftbukkit.CraftHeightMap;
import org.bukkit.craftbukkit.CraftRegistry;
import org.bukkit.craftbukkit.block.CraftBlockStates;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.craftbukkit.scoreboard.CraftCriteria;
import org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations;
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scoreboard.Criteria;
import org.bukkit.scoreboard.DisplaySlot;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;
import static java.util.Objects.requireNonNull;
@DefaultQualifier(NonNull.class)
public class VanillaArgumentProviderImpl implements VanillaArgumentProvider {
@Override
public ArgumentType<EntitySelectorArgumentResolver> entity() {
return this.wrap(EntityArgument.entity(), (result) -> sourceStack -> {
return List.of(result.findSingleEntity((CommandSourceStack) sourceStack).getBukkitEntity());
});
}
@Override
public ArgumentType<EntitySelectorArgumentResolver> entities() {
return this.wrap(EntityArgument.entities(), (result) -> sourceStack -> {
return Lists.transform(result.findEntities((CommandSourceStack) sourceStack), net.minecraft.world.entity.Entity::getBukkitEntity);
});
}
@Override
public ArgumentType<PlayerSelectorArgumentResolver> player() {
return this.wrap(EntityArgument.player(), (result) -> sourceStack -> {
return List.of(result.findSinglePlayer((CommandSourceStack) sourceStack).getBukkitEntity());
});
}
@Override
public ArgumentType<PlayerSelectorArgumentResolver> players() {
return this.wrap(EntityArgument.players(), (result) -> sourceStack -> {
return Lists.transform(result.findPlayers((CommandSourceStack) sourceStack), ServerPlayer::getBukkitEntity);
});
}
@Override
public ArgumentType<PlayerProfileListResolver> playerProfiles() {
return this.wrap(GameProfileArgument.gameProfile(), result -> {
if (result instanceof GameProfileArgument.SelectorResult) {
return sourceStack -> Collections.unmodifiableCollection(Collections2.transform(result.getNames((CommandSourceStack) sourceStack), CraftPlayerProfile::new));
} else {
return sourceStack -> Collections.unmodifiableCollection(Collections2.transform(result.getNames((CommandSourceStack) sourceStack), CraftPlayerProfile::new));
}
});
}
@Override
public ArgumentType<BlockPositionResolver> blockPosition() {
return this.wrap(BlockPosArgument.blockPos(), (result) -> sourceStack -> {
final BlockPos pos = result.getBlockPos((CommandSourceStack) sourceStack);
return MCUtil.toPosition(pos);
});
}
@Override
public ArgumentType<FinePositionResolver> finePosition(final boolean centerIntegers) {
return this.wrap(Vec3Argument.vec3(centerIntegers), (result) -> sourceStack -> {
final Vec3 vec3 = result.getPosition((CommandSourceStack) sourceStack);
return MCUtil.toPosition(vec3);
});
}
@Override
public ArgumentType<BlockState> blockState() {
return this.wrap(BlockStateArgument.block(PaperCommands.INSTANCE.getBuildContext()), (result) -> {
return CraftBlockStates.getBlockState(CraftRegistry.getMinecraftRegistry(), BlockPos.ZERO, result.getState(), result.tag);
});
}
@Override
public ArgumentType<ItemStack> itemStack() {
return this.wrap(ItemArgument.item(PaperCommands.INSTANCE.getBuildContext()), (result) -> {
return CraftItemStack.asBukkitCopy(result.createItemStack(1, true));
});
}
@Override
public ArgumentType<ItemStackPredicate> itemStackPredicate() {
return this.wrap(ItemPredicateArgument.itemPredicate(PaperCommands.INSTANCE.getBuildContext()), type -> itemStack -> type.test(CraftItemStack.asNMSCopy(itemStack)));
}
@Override
public ArgumentType<NamedTextColor> namedColor() {
return this.wrap(ColorArgument.color(), result ->
requireNonNull(
NamedTextColor.namedColor(
requireNonNull(result.getColor(), () -> result + " didn't have a color")
),
() -> result.getColor() + " didn't map to an adventure named color"
)
);
}
@Override
public ArgumentType<Component> component() {
return this.wrap(ComponentArgument.textComponent(PaperCommands.INSTANCE.getBuildContext()), PaperAdventure::asAdventure);
}
@Override
public ArgumentType<Style> style() {
return this.wrap(StyleArgument.style(PaperCommands.INSTANCE.getBuildContext()), PaperAdventure::asAdventure);
}
@Override
public ArgumentType<SignedMessageResolver> signedMessage() {
return this.wrap(MessageArgument.message(), SignedMessageResolverImpl::new);
}
@Override
public ArgumentType<DisplaySlot> scoreboardDisplaySlot() {
return this.wrap(ScoreboardSlotArgument.displaySlot(), CraftScoreboardTranslations::toBukkitSlot);
}
@Override
public ArgumentType<NamespacedKey> namespacedKey() {
return this.wrap(ResourceLocationArgument.id(), CraftNamespacedKey::fromMinecraft);
}
@Override
public ArgumentType<Key> key() {
return this.wrap(ResourceLocationArgument.id(), CraftNamespacedKey::fromMinecraft);
}
@Override
public ArgumentType<IntegerRangeProvider> integerRange() {
return this.wrap(RangeArgument.intRange(), type -> VanillaArgumentProviderImpl.convertToRange(type, integerRange -> () -> integerRange));
}
@Override
public ArgumentType<DoubleRangeProvider> doubleRange() {
return this.wrap(RangeArgument.floatRange(), type -> VanillaArgumentProviderImpl.convertToRange(type, doubleRange -> () -> doubleRange));
}
private static <C extends Number & Comparable<C>, T extends RangeProvider<C>> T convertToRange(final MinMaxBounds<C> bounds, final Function<Range<C>, T> converter) {
if (bounds.isAny()) {
return converter.apply(Range.all());
} else if (bounds.min().isPresent() && bounds.max().isPresent()) {
return converter.apply(Range.closed(bounds.min().get(), bounds.max().get()));
} else if (bounds.max().isPresent()) {
return converter.apply(Range.atMost(bounds.max().get()));
} else if (bounds.min().isPresent()) {
return converter.apply(Range.atLeast(bounds.min().get()));
}
throw new IllegalStateException("This is a bug: " + bounds);
}
@Override
public ArgumentType<World> world() {
return this.wrap(DimensionArgument.dimension(), dimensionLocation -> {
// based on DimensionArgument#getDimension
final ResourceKey<Level> resourceKey = ResourceKey.create(Registries.DIMENSION, dimensionLocation);
final @Nullable ServerLevel serverLevel = MinecraftServer.getServer().getLevel(resourceKey);
if (serverLevel == null) {
throw DimensionArgument.ERROR_INVALID_VALUE.create(dimensionLocation);
} else {
return serverLevel.getWorld();
}
});
}
@Override
public ArgumentType<GameMode> gameMode() {
return this.wrap(GameModeArgument.gameMode(), type -> requireNonNull(GameMode.getByValue(type.getId())));
}
@Override
public ArgumentType<HeightMap> heightMap() {
return this.wrap(HeightmapTypeArgument.heightmap(), CraftHeightMap::fromNMS);
}
@Override
public ArgumentType<UUID> uuid() {
return this.wrap(UuidArgument.uuid());
}
@Override
public ArgumentType<Criteria> objectiveCriteria() {
return this.wrap(ObjectiveCriteriaArgument.criteria(), CraftCriteria::getFromNMS);
}
@Override
public ArgumentType<LookAnchor> entityAnchor() {
return this.wrap(EntityAnchorArgument.anchor(), type -> LookAnchor.valueOf(type.name()));
}
@Override
public ArgumentType<Integer> time(final int minTicks) {
return this.wrap(TimeArgument.time(minTicks));
}
@Override
public ArgumentType<Mirror> templateMirror() {
return this.wrap(TemplateMirrorArgument.templateMirror(), mirror -> Mirror.valueOf(mirror.name()));
}
@Override
public ArgumentType<StructureRotation> templateRotation() {
return this.wrap(TemplateRotationArgument.templateRotation(), mirror -> StructureRotation.valueOf(mirror.name()));
}
@Override
public <T> ArgumentType<TypedKey<T>> resourceKey(final RegistryKey<T> registryKey) {
return this.wrap(
ResourceKeyArgument.key(PaperRegistries.registryToNms(registryKey)),
nmsRegistryKey -> TypedKey.create(registryKey, CraftNamespacedKey.fromMinecraft(nmsRegistryKey.location()))
);
}
@Override
public <T> ArgumentType<T> resource(final RegistryKey<T> registryKey) {
return this.resourceRaw(registryKey);
}
@SuppressWarnings({"unchecked", "rawtypes", "UnnecessaryLocalVariable"})
private <T, K extends Keyed> ArgumentType<T> resourceRaw(final RegistryKey registryKeyRaw) { // TODO remove Keyed
final RegistryKey<K> registryKey = registryKeyRaw;
return (ArgumentType<T>) this.wrap(
ResourceArgument.resource(PaperCommands.INSTANCE.getBuildContext(), PaperRegistries.registryToNms(registryKey)),
resource -> requireNonNull(
RegistryAccess.registryAccess()
.getRegistry(registryKey)
.get(CraftNamespacedKey.fromMinecraft(resource.key().location()))
)
);
}
private <T> ArgumentType<T> wrap(final ArgumentType<T> base) {
return this.wrap(base, identity -> identity);
}
private <B, C> ArgumentType<C> wrap(final ArgumentType<B> base, final ResultConverter<B, C> converter) {
return new NativeWrapperArgumentType<>(base, converter);
}
@FunctionalInterface
interface ResultConverter<T, R> {
R convert(T type) throws CommandSyntaxException;
}
public static final class NativeWrapperArgumentType<M, P> implements ArgumentType<P> {
private final ArgumentType<M> nmsBase;
private final ResultConverter<M, P> converter;
private NativeWrapperArgumentType(final ArgumentType<M> nmsBase, final ResultConverter<M, P> converter) {
this.nmsBase = nmsBase;
this.converter = converter;
}
public ArgumentType<M> nativeNmsArgumentType() {
return this.nmsBase;
}
@Override
public P parse(final StringReader reader) throws CommandSyntaxException {
return this.converter.convert(this.nmsBase.parse(reader));
}
@Override
public <S> CompletableFuture<Suggestions> listSuggestions(final CommandContext<S> context, final SuggestionsBuilder builder) {
return this.nmsBase.listSuggestions(context, builder);
}
@Override
public Collection<String> getExamples() {
return this.nmsBase.getExamples();
}
}
}

View file

@ -0,0 +1,55 @@
package io.papermc.paper.command.brigadier.argument;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.RedirectModifier;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.ParsedArgument;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.CommandNode;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import net.minecraft.commands.synchronization.ArgumentTypeInfos;
import java.util.function.Predicate;
/*
Basically this converts the argument to a different type when parsing.
*/
public class WrappedArgumentCommandNode<NMS, API> extends ArgumentCommandNode<CommandSourceStack, NMS> {
private final ArgumentType<API> pureArgumentType;
public WrappedArgumentCommandNode(
final String name,
final ArgumentType<API> pureArgumentType,
final ArgumentType<NMS> nmsNativeType,
final Command<CommandSourceStack> command,
final Predicate<CommandSourceStack> requirement,
final CommandNode<CommandSourceStack> redirect,
final RedirectModifier<CommandSourceStack> modifier,
final boolean forks,
final SuggestionProvider<CommandSourceStack> customSuggestions
) {
super(name, nmsNativeType, command, requirement, redirect, modifier, forks, customSuggestions);
if (!ArgumentTypeInfos.isClassRecognized(nmsNativeType.getClass())) {
// Is this argument an NMS argument?
throw new IllegalArgumentException("Unexpected argument type was passed: " + nmsNativeType.getClass() + ". This should be an NMS type!");
}
this.pureArgumentType = pureArgumentType;
}
// See ArgumentCommandNode#parse
@Override
public void parse(final StringReader reader, final CommandContextBuilder<CommandSourceStack> contextBuilder) throws CommandSyntaxException {
final int start = reader.getCursor();
final API result = this.pureArgumentType.parse(reader); // Use the api argument parser
final ParsedArgument<CommandSourceStack, API> parsed = new ParsedArgument<>(start, reader.getCursor(), result); // Return an API parsed argument instead.
contextBuilder.withArgument(this.getName(), parsed);
contextBuilder.withNode(this, parsed.getRange());
}
}

View file

@ -0,0 +1,338 @@
package io.papermc.paper.command.brigadier.bukkit;
import com.google.common.collect.Iterators;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.tree.CommandNode;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.command.brigadier.PaperBrigadier;
import io.papermc.paper.command.brigadier.PaperCommands;
import io.papermc.paper.command.brigadier.PluginVanillaCommandWrapper;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.bukkit.command.Command;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/*
This map is supposed to act as a legacy bridge for the command map and the command dispatcher.
*/
public class BukkitBrigForwardingMap extends HashMap<String, Command> {
public static BukkitBrigForwardingMap INSTANCE = new BukkitBrigForwardingMap();
private final EntrySet entrySet = new EntrySet();
private final KeySet keySet = new KeySet();
private final Values values = new Values();
// Previous dispatcher used to get commands to migrate to another dispatcher
public CommandDispatcher<CommandSourceStack> getDispatcher() {
return PaperCommands.INSTANCE.getDispatcherInternal();
}
@Override
public int size() {
return this.getDispatcher().getRoot().getChildren().size();
}
@Override
public boolean isEmpty() {
return this.size() != 0;
}
@Override
public boolean containsKey(Object key) {
if (!(key instanceof String stringKey)) {
return false;
}
// Do any children match?
return this.getDispatcher().getRoot().getChild(stringKey) != null;
}
@Override
public boolean containsValue(@Nullable final Object value) {
if (!(value instanceof Command)) {
return false;
}
for (CommandNode<CommandSourceStack> child : this.getDispatcher().getRoot().getChildren()) {
// If child is a bukkit command node, we can convert it!
if (child instanceof BukkitCommandNode bukkitCommandNode) {
return bukkitCommandNode.getBukkitCommand().equals(value);
}
}
return false;
}
@Override
public Command get(Object key) {
CommandNode<?> node = this.getDispatcher().getRoot().getChild((String) key);
if (node == null) {
return null;
}
if (node instanceof BukkitCommandNode bukkitCommandNode) {
return bukkitCommandNode.getBukkitCommand();
}
return PaperBrigadier.wrapNode(node);
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Nullable
@Override
public Command put(String key, Command value) {
Command old = this.get(key);
this.getDispatcher().getRoot().removeCommand(key); // Override previous command
if (value instanceof PluginVanillaCommandWrapper wrapper && wrapper.getName().equals(key)) {
// Don't break when some plugin tries to remove and add back a plugin command registered with modern API...
this.getDispatcher().getRoot().addChild((CommandNode) wrapper.vanillaCommand);
} else {
this.getDispatcher().getRoot().addChild(BukkitCommandNode.of(key, value));
}
return old;
}
@Override
public Command remove(Object key) {
if (!(key instanceof String string)) {
return null;
}
Command old = this.get(key);
if (old != null) {
this.getDispatcher().getRoot().removeCommand(string);
}
return old;
}
@Override
public boolean remove(Object key, Object value) {
Command old = this.get(key);
if (Objects.equals(old, value)) {
this.remove(key);
return true;
}
return false;
}
@Override
public void putAll(@NotNull Map<? extends String, ? extends Command> m) {
for (Entry<? extends String, ? extends Command> entry : m.entrySet()) {
this.put(entry.getKey(), entry.getValue());
}
}
@Override
public void clear() {
this.getDispatcher().getRoot().clearAll();
}
@NotNull
@Override
public Set<String> keySet() {
return this.keySet;
}
@NotNull
@Override
public Collection<Command> values() {
return this.values;
}
@NotNull
@Override
public Set<Entry<String, Command>> entrySet() {
return this.entrySet;
}
final class Values extends AbstractCollection<Command> {
@Override
public Iterator<Command> iterator() {
// AVOID CME since commands can modify multiple commands now through alises, which means it may appear in the iterator even if removed.
// Oh well!
Iterator<CommandNode<CommandSourceStack>> iterator = new ArrayList<>(BukkitBrigForwardingMap.this.getDispatcher().getRoot().getChildren()).iterator();
return new Iterator<>() {
private CommandNode<CommandSourceStack> lastFetched;
@Override
public void remove() {
if (this.lastFetched == null) {
throw new IllegalStateException("next not yet called");
}
BukkitBrigForwardingMap.this.remove(this.lastFetched.getName());
iterator.remove();
}
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Command next() {
CommandNode<CommandSourceStack> next = iterator.next();
this.lastFetched = next;
if (next instanceof BukkitCommandNode bukkitCommandNode) {
return bukkitCommandNode.getBukkitCommand();
} else {
return PaperBrigadier.wrapNode(next);
}
}
};
}
@Override
public int size() {
return BukkitBrigForwardingMap.this.getDispatcher().getRoot().getChildren().size();
}
@Override
public void clear() {
BukkitBrigForwardingMap.this.clear();
}
}
final class KeySet extends AbstractSet<String> {
@Override
public int size() {
return BukkitBrigForwardingMap.this.size();
}
@Override
public void clear() {
BukkitBrigForwardingMap.this.clear();
}
@Override
public Iterator<String> iterator() {
return Iterators.transform(BukkitBrigForwardingMap.this.values.iterator(), Command::getName); // Wrap around the values iterator for consistancy
}
@Override
public boolean contains(Object o) {
return BukkitBrigForwardingMap.this.containsKey(o);
}
@Override
public boolean remove(Object o) {
return BukkitBrigForwardingMap.this.remove(o) != null;
}
@Override
public Spliterator<String> spliterator() {
return this.entryStream().spliterator();
}
@Override
public void forEach(Consumer<? super String> action) {
this.entryStream().forEach(action);
}
private Stream<String> entryStream() {
return BukkitBrigForwardingMap.this.getDispatcher().getRoot().getChildren().stream().map(CommandNode::getName);
}
}
final class EntrySet extends AbstractSet<Entry<String, Command>> {
@Override
public int size() {
return BukkitBrigForwardingMap.this.size();
}
@Override
public void clear() {
BukkitBrigForwardingMap.this.clear();
}
@Override
public Iterator<Entry<String, Command>> iterator() {
return this.entryStream().iterator();
}
@Override
public boolean contains(Object o) {
if (!(o instanceof Map.Entry<?, ?> entry)) {
return false;
}
Object key = entry.getKey();
Command candidate = get(key);
return candidate != null && candidate.equals(entry.getValue());
}
@Override
public boolean remove(Object o) {
if (o instanceof Map.Entry<?, ?> e) {
Object key = e.getKey();
Object value = e.getValue();
return BukkitBrigForwardingMap.this.remove(key, value);
}
return false;
}
@Override
public Spliterator<Entry<String, Command>> spliterator() {
return this.entryStream().spliterator();
}
@Override
public void forEach(Consumer<? super Entry<String, Command>> action) {
this.entryStream().forEach(action);
}
private Stream<Map.Entry<String, Command>> entryStream() {
return BukkitBrigForwardingMap.this.getDispatcher().getRoot().getChildren().stream().map(BukkitBrigForwardingMap.this::nodeToEntry);
}
}
private Map.Entry<String, Command> nodeToEntry(CommandNode<?> node) {
if (node instanceof BukkitCommandNode bukkitCommandNode) {
return this.mutableEntry(bukkitCommandNode.getName(), bukkitCommandNode.getBukkitCommand());
} else {
Command wrapped = PaperBrigadier.wrapNode(node);
return this.mutableEntry(node.getName(), wrapped);
}
}
private Map.Entry<String, Command> mutableEntry(String key, Command command) {
return new Entry<>() {
@Override
public String getKey() {
return key;
}
@Override
public Command getValue() {
return command;
}
@Override
public Command setValue(Command value) {
return BukkitBrigForwardingMap.this.put(key, value);
}
};
}
}

View file

@ -0,0 +1,138 @@
package io.papermc.paper.command.brigadier.bukkit;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.LiteralCommandNode;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import java.util.ArrayList;
import net.minecraft.commands.CommandSource;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandException;
import org.bukkit.command.CommandSender;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import org.bukkit.entity.Player;
import org.bukkit.event.server.TabCompleteEvent;
public class BukkitCommandNode extends LiteralCommandNode<CommandSourceStack> {
private final Command command;
private BukkitCommandNode(String literal, Command command, BukkitBrigCommand bukkitBrigCommand) {
super(
literal, bukkitBrigCommand, source -> {
// If the source is null, assume it's true.
// As bukkit doesn't really map the command sender well in all cases
if (source instanceof net.minecraft.commands.CommandSourceStack commandSourceStack && commandSourceStack.source == CommandSource.NULL) {
return true;
} else {
return command.testPermissionSilent(source.getSender());
}
},
null, null, false
);
this.command = command;
}
public static BukkitCommandNode of(String name, Command command) {
BukkitBrigCommand bukkitBrigCommand = new BukkitBrigCommand(command, name);
BukkitCommandNode commandNode = new BukkitCommandNode(name, command, bukkitBrigCommand);
commandNode.addChild(
RequiredArgumentBuilder.<CommandSourceStack, String>argument("args", StringArgumentType.greedyString())
.suggests(new BukkitBrigSuggestionProvider(command, name))
.executes(bukkitBrigCommand).build()
);
return commandNode;
}
public Command getBukkitCommand() {
return this.command;
}
public static class BukkitBrigCommand implements com.mojang.brigadier.Command<CommandSourceStack> {
private final org.bukkit.command.Command command;
private final String literal;
BukkitBrigCommand(org.bukkit.command.Command command, String literal) {
this.command = command;
this.literal = literal;
}
@Override
public int run(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
CommandSender sender = context.getSource().getSender();
String content = context.getRange().get(context.getInput());
String[] args = org.apache.commons.lang3.StringUtils.split(content, ' '); // fix adjacent spaces (from console/plugins) causing empty array elements
// Note: we don't return the result of target.execute as thats success / failure, we return handled (true) or not handled (false)
this.command.execute(sender, this.literal, Arrays.copyOfRange(args, 1, args.length));
// return true as command was handled
return 1;
}
}
static class BukkitBrigSuggestionProvider implements SuggestionProvider<CommandSourceStack> {
private final org.bukkit.command.Command command;
private final String literal;
BukkitBrigSuggestionProvider(org.bukkit.command.Command command, String literal) {
this.command = command;
this.literal = literal;
}
@Override
public CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) throws CommandSyntaxException {
// Paper start
org.bukkit.command.CommandSender sender = context.getSource().getSender();
String[] args = builder.getRemaining().split(" ", -1); // We need the command included -- Set limit to -1, allow for trailing spaces
List<String> results = null;
Location pos = context.getSource().getLocation();
try {
results = this.command.tabComplete(sender, this.literal, args, pos.clone());
} catch (CommandException ex) {
sender.sendMessage(ChatColor.RED + "An internal error occurred while attempting to tab-complete this command");
Bukkit.getServer().getLogger().log(Level.SEVERE, "Exception when " + sender.getName() + " attempted to tab complete " + builder.getRemaining(), ex);
}
if (sender instanceof final Player player) {
TabCompleteEvent tabEvent = new org.bukkit.event.server.TabCompleteEvent(player, builder.getInput(), results != null ? results : new ArrayList<>(), true, pos); // Paper - AsyncTabCompleteEvent
if (!tabEvent.callEvent()) {
results = null;
} else {
results = tabEvent.getCompletions();
}
}
// Paper end
if (results == null) {
return builder.buildFuture();
}
// Defaults to sub nodes, but we have just one giant args node, so offset accordingly
builder = builder.createOffset(builder.getInput().lastIndexOf(' ') + 1);
for (String s : results) {
builder.suggest(s);
}
return builder.buildFuture();
}
}
}

View file

@ -275,11 +275,11 @@ public final class CraftServer implements Server {
private final Logger logger = Logger.getLogger("Minecraft");
private final ServicesManager servicesManager = new SimpleServicesManager();
private final CraftScheduler scheduler = new CraftScheduler();
private final CraftCommandMap commandMap = new CraftCommandMap(this);
private final CraftCommandMap commandMap; // Paper - Move down
private final SimpleHelpMap helpMap = new SimpleHelpMap(this);
private final StandardMessenger messenger = new StandardMessenger();
private final SimplePluginManager pluginManager = new SimplePluginManager(this, commandMap);
public final io.papermc.paper.plugin.manager.PaperPluginManagerImpl paperPluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(this, this.commandMap, pluginManager); {this.pluginManager.paperPluginManager = this.paperPluginManager;} // Paper
private final SimplePluginManager pluginManager; // Paper - Move down
public final io.papermc.paper.plugin.manager.PaperPluginManagerImpl paperPluginManager; // Paper
private final StructureManager structureManager;
protected final DedicatedServer console;
protected final DedicatedPlayerList playerList;
@ -419,6 +419,12 @@ public final class CraftServer implements Server {
this.serverLinks = new CraftServerLinks(console);
Bukkit.setServer(this);
// Paper start
this.commandMap = new CraftCommandMap(this);
this.pluginManager = new SimplePluginManager(this, commandMap);
this.paperPluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(this, this.commandMap, pluginManager);
this.pluginManager.paperPluginManager = this.paperPluginManager;
// Paper end
CraftRegistry.setMinecraftRegistry(console.registryAccess());
@ -617,48 +623,11 @@ public final class CraftServer implements Server {
}
private void setVanillaCommands(boolean first) { // Spigot
Commands dispatcher = this.console.vanillaCommandDispatcher;
// Build a list of all Vanilla commands and create wrappers
for (CommandNode<CommandSourceStack> cmd : dispatcher.getDispatcher().getRoot().getChildren()) {
// Spigot start
VanillaCommandWrapper wrapper = new VanillaCommandWrapper(dispatcher, cmd);
if (org.spigotmc.SpigotConfig.replaceCommands.contains( wrapper.getName() ) ) {
if (first) {
this.commandMap.register("minecraft", wrapper);
}
} else if (!first) {
this.commandMap.register("minecraft", wrapper);
}
// Spigot end
}
// Paper - Replace implementation
}
public void syncCommands() {
// Clear existing commands
Commands dispatcher = this.console.resources.managers().commands = new Commands();
// Register all commands, vanilla ones will be using the old dispatcher references
for (Map.Entry<String, Command> entry : this.commandMap.getKnownCommands().entrySet()) {
String label = entry.getKey();
Command command = entry.getValue();
if (command instanceof VanillaCommandWrapper) {
LiteralCommandNode<CommandSourceStack> node = (LiteralCommandNode<CommandSourceStack>) ((VanillaCommandWrapper) command).vanillaCommand;
if (!node.getLiteral().equals(label)) {
LiteralCommandNode<CommandSourceStack> clone = new LiteralCommandNode(label, node.getCommand(), node.getRequirement(), node.getRedirect(), node.getRedirectModifier(), node.isFork());
for (CommandNode<CommandSourceStack> child : node.getChildren()) {
clone.addChild(child);
}
node = clone;
}
dispatcher.getDispatcher().getRoot().addChild(node);
} else {
new BukkitCommandWrapper(this, entry.getValue()).register(dispatcher.getDispatcher(), label);
}
}
Commands dispatcher = this.getHandle().getServer().getCommands(); // Paper - We now register directly to the dispatcher.
// Refresh commands
for (ServerPlayer player : this.getHandle().players) {
@ -1045,17 +1014,31 @@ public final class CraftServer implements Server {
return true;
}
// Spigot start
if (!org.spigotmc.SpigotConfig.unknownCommandMessage.isEmpty()) {
// Paper start
org.bukkit.event.command.UnknownCommandEvent event = new org.bukkit.event.command.UnknownCommandEvent(sender, commandLine, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.unknownCommandMessage));
this.getPluginManager().callEvent(event);
if (event.message() != null) {
sender.sendMessage(event.message());
}
// Paper end
return this.dispatchCommand(VanillaCommandWrapper.getListener(sender), commandLine);
}
public boolean dispatchCommand(CommandSourceStack sourceStack, String commandLine) {
net.minecraft.commands.Commands commands = this.getHandle().getServer().getCommands();
com.mojang.brigadier.CommandDispatcher<CommandSourceStack> dispatcher = commands.getDispatcher();
com.mojang.brigadier.ParseResults<CommandSourceStack> results = dispatcher.parse(commandLine, sourceStack);
CommandSender sender = sourceStack.getBukkitSender();
String[] args = org.apache.commons.lang3.StringUtils.split(commandLine, ' '); // Paper - fix adjacent spaces (from console/plugins) causing empty array elements
Command target = this.commandMap.getCommand(args[0].toLowerCase(java.util.Locale.ENGLISH));
try {
commands.performCommand(results, commandLine, commandLine, true);
} catch (CommandException ex) {
this.pluginManager.callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args))); // Paper
//target.timings.stopTiming(); // Spigot // Paper
throw ex;
} catch (Throwable ex) {
//target.timings.stopTiming(); // Spigot // Paper
String msg = "Unhandled exception executing '" + commandLine + "' in " + target;
this.pluginManager.callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerCommandException(ex, target, sender, args))); // Paper
throw new CommandException(msg, ex);
}
// Spigot end
// Paper end
return false;
}
@ -1064,7 +1047,7 @@ public final class CraftServer implements Server {
public void reload() {
// Paper start - lifecycle events
if (io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.blocksPluginReloading()) {
throw new IllegalStateException("A lifecycle event handler has been registered which makes reloading plugins not possible");
throw new IllegalStateException(org.bukkit.command.defaults.ReloadCommand.RELOADING_DISABLED_MESSAGE);
}
// Paper end - lifecycle events
org.spigotmc.WatchdogThread.hasStarted = false; // Paper - Disable watchdog early timeout on reload
@ -1119,8 +1102,9 @@ public final class CraftServer implements Server {
}
Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper
this.commandMap.clearCommands(); // Paper - Move command reloading up
this.pluginManager.clearPlugins();
this.commandMap.clearCommands();
// Paper - move up
// Paper start
for (Plugin plugin : pluginClone) {
entityMetadata.removeAll(plugin);
@ -1160,6 +1144,12 @@ public final class CraftServer implements Server {
this.enablePlugins(PluginLoadOrder.STARTUP);
this.enablePlugins(PluginLoadOrder.POSTWORLD);
if (io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper != null) io.papermc.paper.plugin.PluginInitializerManager.instance().pluginRemapper.pluginsEnabled(); // Paper - Remap plugins
// Paper start - brigadier command API
io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.setValid(); // to clear invalid state for event fire below
io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callReloadableRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.COMMANDS, io.papermc.paper.command.brigadier.PaperCommands.INSTANCE, org.bukkit.plugin.Plugin.class, io.papermc.paper.plugin.lifecycle.event.registrar.ReloadableRegistrarEvent.Cause.RELOAD); // call commands event for regular plugins
this.helpMap.initializeCommands();
this.syncCommands(); // Refresh commands after event
// Paper end - brigadier command API
this.getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.RELOAD));
org.spigotmc.WatchdogThread.hasStarted = true; // Paper - Disable watchdog early timeout on reload
}

View file

@ -20,6 +20,7 @@ import org.bukkit.command.CommandException;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.CraftServer;
@Deprecated(forRemoval = true) // Paper - Don't use
public class BukkitCommandWrapper implements com.mojang.brigadier.Command<CommandSourceStack>, Predicate<CommandSourceStack>, SuggestionProvider<CommandSourceStack>, com.destroystokyo.paper.brigadier.BukkitBrigadierCommand<CommandSourceStack> { // Paper
private final CraftServer server;

View file

@ -8,7 +8,7 @@ import org.bukkit.command.SimpleCommandMap;
public class CraftCommandMap extends SimpleCommandMap {
public CraftCommandMap(Server server) {
super(server);
super(server, io.papermc.paper.command.brigadier.bukkit.BukkitBrigForwardingMap.INSTANCE);
}
public Map<String, Command> getKnownCommands() {

View file

@ -24,14 +24,26 @@ import org.bukkit.craftbukkit.entity.CraftMinecartCommand;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.minecart.CommandMinecart;
public final class VanillaCommandWrapper extends BukkitCommand {
public class VanillaCommandWrapper extends BukkitCommand { // Paper
private final Commands dispatcher;
//private final Commands dispatcher; // Paper
public final CommandNode<CommandSourceStack> vanillaCommand;
// Paper start
public VanillaCommandWrapper(String name, String description, String usageMessage, List<String> aliases, CommandNode<CommandSourceStack> vanillaCommand) {
super(name, description, usageMessage, aliases);
//this.dispatcher = dispatcher; // Paper
this.vanillaCommand = vanillaCommand;
}
Commands commands() {
return net.minecraft.server.MinecraftServer.getServer().getCommands();
}
// Paper end
public VanillaCommandWrapper(Commands dispatcher, CommandNode<CommandSourceStack> vanillaCommand) {
super(vanillaCommand.getName(), "A Mojang provided command.", vanillaCommand.getUsageText(), Collections.EMPTY_LIST);
this.dispatcher = dispatcher;
// this.dispatcher = dispatcher; // Paper
this.vanillaCommand = vanillaCommand;
this.setPermission(VanillaCommandWrapper.getPermission(vanillaCommand));
}
@ -41,7 +53,7 @@ public final class VanillaCommandWrapper extends BukkitCommand {
if (!this.testPermission(sender)) return true;
CommandSourceStack icommandlistener = VanillaCommandWrapper.getListener(sender);
this.dispatcher.performPrefixedCommand(icommandlistener, this.toDispatcher(args, this.getName()), this.toDispatcher(args, commandLabel));
this.commands().performPrefixedCommand(icommandlistener, this.toDispatcher(args, this.getName()), this.toDispatcher(args, commandLabel)); // Paper
return true;
}
@ -52,10 +64,10 @@ public final class VanillaCommandWrapper extends BukkitCommand {
Preconditions.checkArgument(alias != null, "Alias cannot be null");
CommandSourceStack icommandlistener = VanillaCommandWrapper.getListener(sender);
ParseResults<CommandSourceStack> parsed = this.dispatcher.getDispatcher().parse(this.toDispatcher(args, this.getName()), icommandlistener);
ParseResults<CommandSourceStack> parsed = this.commands().getDispatcher().parse(this.toDispatcher(args, this.getName()), icommandlistener); // Paper
List<String> results = new ArrayList<>();
this.dispatcher.getDispatcher().getCompletionSuggestions(parsed).thenAccept((suggestions) -> {
this.commands().getDispatcher().getCompletionSuggestions(parsed).thenAccept((suggestions) -> { // Paper
suggestions.getList().forEach((s) -> results.add(s.getText()));
});
@ -116,4 +128,15 @@ public final class VanillaCommandWrapper extends BukkitCommand {
private String toDispatcher(String[] args, String name) {
return name + ((args.length > 0) ? " " + Joiner.on(' ').join(args) : "");
}
// Paper start
@Override
public boolean canBeOverriden() {
return true;
}
@Override
public boolean isRegistered() {
return true;
}
// Paper end
}

View file

@ -200,15 +200,18 @@ public class SimpleHelpMap implements HelpMap {
}
private String getCommandPluginName(Command command) {
// Paper start - Move up
if (command instanceof PluginIdentifiableCommand) {
return ((PluginIdentifiableCommand) command).getPlugin().getName();
}
// Paper end
if (command instanceof VanillaCommandWrapper) {
return "Minecraft";
}
if (command instanceof BukkitCommand) {
return "Bukkit";
}
if (command instanceof PluginIdentifiableCommand) {
return ((PluginIdentifiableCommand) command).getPlugin().getName();
}
// Paper - Move PluginIdentifiableCommand instanceof check to allow brig commands
return null;
}

View file

@ -53,7 +53,13 @@ public final class CraftCriteria implements Criteria {
return RenderType.values()[this.criteria.getDefaultRenderType().ordinal()];
}
static CraftCriteria getFromNMS(Objective objective) {
// Paper start
public static CraftCriteria getFromNMS(ObjectiveCriteria criteria) {
return java.util.Objects.requireNonNullElseGet(CraftCriteria.DEFAULTS.get(criteria.getName()), () -> new CraftCriteria(criteria));
}
// Paper end
public static CraftCriteria getFromNMS(Objective objective) {
return java.util.Objects.requireNonNullElseGet(CraftCriteria.DEFAULTS.get(objective.getCriteria().getName()), () -> new CraftCriteria(objective.getCriteria())); // Paper
}

View file

@ -0,0 +1 @@
io.papermc.paper.command.brigadier.CommandBuilderImpl$ProviderImpl

View file

@ -0,0 +1 @@
io.papermc.paper.command.brigadier.MessageComponentSerializerImpl

View file

@ -0,0 +1 @@
io.papermc.paper.command.brigadier.argument.VanillaArgumentProviderImpl

View file

@ -0,0 +1,102 @@
package io.papermc.paper.command.brigadier;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import io.papermc.paper.command.brigadier.bukkit.BukkitBrigForwardingMap;
import java.util.List;
import java.util.Map;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.bukkit.command.CommandSender;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.support.RegistryHelper;
import org.bukkit.support.environment.AllFeatures;
import org.bukkit.support.environment.Normal;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@Normal
public class BukkitCommandConversionTest {
private CommandSender getSender() {
return Mockito.mock(CommandSender.class);
}
@Test
public void test() throws CommandSyntaxException {
CommandSender sender = this.getSender();
CommandSourceStack object = Mockito.mock(CommandSourceStack.class);
Mockito.when(object.getLocation()).thenReturn(new Location(null, 0, 0, 0));;
CommandDispatcher dispatcher = RegistryHelper.getDataPack().commands.getDispatcher();
dispatcher.setConsumer((context, success, result) -> {});
CommandMap commandMap = new SimpleCommandMap(Bukkit.getServer(), new BukkitBrigForwardingMap());
Map<String, Command> stringCommandMap = commandMap.getKnownCommands();
// All commands should be mirrored -- or equal
int commandMapSize = stringCommandMap.values().size();
ExampleCommand exampleCommand = new ExampleCommand();
Assertions.assertEquals(commandMapSize, dispatcher.getRoot().getChildren().size());
// Register a new command
commandMap.register("test", exampleCommand);
Assertions.assertEquals(commandMapSize + (3 * 2), stringCommandMap.values().size()); // Make sure commands are accounted for, including those with namespaced keys
// Test Registration
for (String alias : exampleCommand.getAliases()) {
Assertions.assertEquals(stringCommandMap.get(alias), exampleCommand);
Assertions.assertEquals(stringCommandMap.get("test:" + alias), exampleCommand);
}
// Test command instance equality
Assertions.assertEquals(stringCommandMap.get(exampleCommand.getName()), exampleCommand);
Assertions.assertEquals(stringCommandMap.get("test:" + exampleCommand.getName()), exampleCommand);
// Test command map execution
commandMap.dispatch(sender, "main-example example");
Assertions.assertEquals(exampleCommand.invocations, 1);
Assertions.assertEquals(commandMap.tabComplete(sender, "main-example 1 2"), List.of("complete"));
// Test dispatcher execution
dispatcher.execute("main-example example", object);
Assertions.assertEquals(exampleCommand.invocations, 2);
dispatcher.execute("test:example2 example", object);
Assertions.assertEquals(exampleCommand.invocations, 3);
Suggestions suggestions = (Suggestions) dispatcher.getCompletionSuggestions(dispatcher.parse("main-example 1 2", object)).join();
Assertions.assertEquals(suggestions.getList().get(0).getText(), "complete");
// Test command map removal
commandMap.getKnownCommands().remove("test");
Assertions.assertNull(commandMap.getCommand("test"));
Assertions.assertNull(dispatcher.getRoot().getChild("test"));
}
private static class ExampleCommand extends Command {
int invocations;
protected ExampleCommand() {
super("main-example", "This is an example.", "", List.of("example", "example2"));
}
@Override
public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
Assertions.assertEquals(args[0], "example");
this.invocations++;
return true;
}
@Override
public @NotNull List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException {
Assertions.assertEquals(args.length, 2);
return List.of("complete");
}
}
}

View file

@ -87,7 +87,7 @@ public final class DummyServerHelper {
// Paper start - testing additions
final Thread currentThread = Thread.currentThread();
when(instance.isPrimaryThread()).thenAnswer(ignored -> Thread.currentThread().equals(currentThread));
final org.bukkit.plugin.PluginManager pluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(instance, new org.bukkit.command.SimpleCommandMap(instance), null);
final org.bukkit.plugin.PluginManager pluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(instance, new org.bukkit.command.SimpleCommandMap(instance, new java.util.HashMap<>()), null);
when(instance.getPluginManager()).thenReturn(pluginManager);
// Paper end - testing additions