package net.minecraft.server.commands; import com.google.common.annotations.VisibleForTesting; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.ContextChain; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.datafixers.util.Pair; import java.util.Collection; import net.minecraft.commands.CommandResultCallback; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.ExecutionCommandSource; import net.minecraft.commands.FunctionInstantiationException; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.commands.arguments.CompoundTagArgument; import net.minecraft.commands.arguments.NbtPathArgument; import net.minecraft.commands.arguments.NbtPathArgument.NbtPath; import net.minecraft.commands.arguments.item.FunctionArgument; import net.minecraft.commands.execution.ChainModifiers; import net.minecraft.commands.execution.CustomCommandExecutor; import net.minecraft.commands.execution.ExecutionControl; import net.minecraft.commands.execution.tasks.CallFunction; import net.minecraft.commands.execution.tasks.FallthroughTask; import net.minecraft.commands.functions.CommandFunction; import net.minecraft.commands.functions.InstantiatedFunction; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.ComponentUtils; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.ServerFunctionManager; import net.minecraft.server.commands.data.DataAccessor; import net.minecraft.server.commands.data.DataCommands; import net.minecraft.server.commands.data.DataCommands.DataProvider; import org.jetbrains.annotations.Nullable; public class FunctionCommand { private static final DynamicCommandExceptionType ERROR_ARGUMENT_NOT_COMPOUND = new DynamicCommandExceptionType( object -> Component.translatableEscape("commands.function.error.argument_not_compound", object) ); static final DynamicCommandExceptionType ERROR_NO_FUNCTIONS = new DynamicCommandExceptionType( object -> Component.translatableEscape("commands.function.scheduled.no_functions", object) ); @VisibleForTesting public static final Dynamic2CommandExceptionType ERROR_FUNCTION_INSTANTATION_FAILURE = new Dynamic2CommandExceptionType( (object, object2) -> Component.translatableEscape("commands.function.instantiationFailure", object, object2) ); public static final SuggestionProvider SUGGEST_FUNCTION = (commandContext, suggestionsBuilder) -> { ServerFunctionManager serverFunctionManager = commandContext.getSource().getServer().getFunctions(); SharedSuggestionProvider.suggestResource(serverFunctionManager.getTagNames(), suggestionsBuilder, "#"); return SharedSuggestionProvider.suggestResource(serverFunctionManager.getFunctionNames(), suggestionsBuilder); }; static final FunctionCommand.Callbacks FULL_CONTEXT_CALLBACKS = new FunctionCommand.Callbacks() { public void signalResult(CommandSourceStack commandSourceStack, ResourceLocation resourceLocation, int i) { commandSourceStack.sendSuccess(() -> Component.translatable("commands.function.result", Component.translationArg(resourceLocation), i), true); } }; public static void register(CommandDispatcher dispatcher) { LiteralArgumentBuilder literalArgumentBuilder = Commands.literal("with"); for (DataProvider dataProvider : DataCommands.SOURCE_PROVIDERS) { dataProvider.wrap(literalArgumentBuilder, argumentBuilder -> argumentBuilder.executes(new FunctionCommand.FunctionCustomExecutor() { @Override protected CompoundTag arguments(CommandContext context) throws CommandSyntaxException { return dataProvider.access(context).getData(); } }).then(Commands.argument("path", NbtPathArgument.nbtPath()).executes(new FunctionCommand.FunctionCustomExecutor() { @Override protected CompoundTag arguments(CommandContext context) throws CommandSyntaxException { return FunctionCommand.getArgumentTag(NbtPathArgument.getPath(context, "path"), dataProvider.access(context)); } }))); } dispatcher.register( Commands.literal("function") .requires(commandSourceStack -> commandSourceStack.hasPermission(2)) .then(Commands.argument("name", FunctionArgument.functions()).suggests(SUGGEST_FUNCTION).executes(new FunctionCommand.FunctionCustomExecutor() { @Nullable @Override protected CompoundTag arguments(CommandContext context) { return null; } }).then(Commands.argument("arguments", CompoundTagArgument.compoundTag()).executes(new FunctionCommand.FunctionCustomExecutor() { @Override protected CompoundTag arguments(CommandContext context) { return CompoundTagArgument.getCompoundTag(context, "arguments"); } })).then(literalArgumentBuilder)) ); } static CompoundTag getArgumentTag(NbtPath nbtPath, DataAccessor dataAccessor) throws CommandSyntaxException { Tag tag = DataCommands.getSingleTag(nbtPath, dataAccessor); if (tag instanceof CompoundTag compoundTag) { return compoundTag; } else { throw ERROR_ARGUMENT_NOT_COMPOUND.create(tag.getType().getName()); } } public static CommandSourceStack modifySenderForExecution(CommandSourceStack source) { return source.withSuppressedOutput().withMaximumPermission(2); } public static > void queueFunctions( Collection> functions, @Nullable CompoundTag arguments, T originalSource, T source, ExecutionControl executionControl, FunctionCommand.Callbacks callbacks, ChainModifiers chainModifiers ) throws CommandSyntaxException { if (chainModifiers.isReturn()) { queueFunctionsAsReturn(functions, arguments, originalSource, source, executionControl, callbacks); } else { queueFunctionsNoReturn(functions, arguments, originalSource, source, executionControl, callbacks); } } private static > void instantiateAndQueueFunctions( @Nullable CompoundTag arguments, ExecutionControl executionControl, CommandDispatcher dispatcher, T source, CommandFunction function, ResourceLocation functionId, CommandResultCallback resultCallback, boolean returnParentFrame ) throws CommandSyntaxException { try { InstantiatedFunction instantiatedFunction = function.instantiate(arguments, dispatcher); executionControl.queueNext(new CallFunction<>(instantiatedFunction, resultCallback, returnParentFrame).bind(source)); } catch (FunctionInstantiationException var9) { throw ERROR_FUNCTION_INSTANTATION_FAILURE.create(functionId, var9.messageComponent()); } } private static > CommandResultCallback decorateOutputIfNeeded( T source, FunctionCommand.Callbacks callbacks, ResourceLocation function, CommandResultCallback resultCallback ) { return source.isSilent() ? resultCallback : (bl, i) -> { callbacks.signalResult(source, function, i); resultCallback.onResult(bl, i); }; } private static > void queueFunctionsAsReturn( Collection> functions, @Nullable CompoundTag arguments, T originalSource, T source, ExecutionControl exectutionControl, FunctionCommand.Callbacks callbacks ) throws CommandSyntaxException { CommandDispatcher commandDispatcher = originalSource.dispatcher(); T executionCommandSource = source.clearCallbacks(); CommandResultCallback commandResultCallback = CommandResultCallback.chain(originalSource.callback(), exectutionControl.currentFrame().returnValueConsumer()); for (CommandFunction commandFunction : functions) { ResourceLocation resourceLocation = commandFunction.id(); CommandResultCallback commandResultCallback2 = decorateOutputIfNeeded(originalSource, callbacks, resourceLocation, commandResultCallback); instantiateAndQueueFunctions( arguments, exectutionControl, commandDispatcher, executionCommandSource, commandFunction, resourceLocation, commandResultCallback2, true ); } exectutionControl.queueNext(FallthroughTask.instance()); } private static > void queueFunctionsNoReturn( Collection> functions, @Nullable CompoundTag arguments, T originalSource, T source, ExecutionControl executionControl, FunctionCommand.Callbacks callbacks ) throws CommandSyntaxException { CommandDispatcher commandDispatcher = originalSource.dispatcher(); T executionCommandSource = source.clearCallbacks(); CommandResultCallback commandResultCallback = originalSource.callback(); if (!functions.isEmpty()) { if (functions.size() == 1) { CommandFunction commandFunction = (CommandFunction)functions.iterator().next(); ResourceLocation resourceLocation = commandFunction.id(); CommandResultCallback commandResultCallback2 = decorateOutputIfNeeded(originalSource, callbacks, resourceLocation, commandResultCallback); instantiateAndQueueFunctions( arguments, executionControl, commandDispatcher, executionCommandSource, commandFunction, resourceLocation, commandResultCallback2, false ); } else if (commandResultCallback == CommandResultCallback.EMPTY) { for (CommandFunction commandFunction2 : functions) { ResourceLocation resourceLocation2 = commandFunction2.id(); CommandResultCallback commandResultCallback3 = decorateOutputIfNeeded(originalSource, callbacks, resourceLocation2, commandResultCallback); instantiateAndQueueFunctions( arguments, executionControl, commandDispatcher, executionCommandSource, commandFunction2, resourceLocation2, commandResultCallback3, false ); } } else { class Accumulator { boolean anyResult; int sum; public void add(int result) { this.anyResult = true; this.sum += result; } } Accumulator lv = new Accumulator(); CommandResultCallback commandResultCallback4 = (bl, i) -> lv.add(i); for (CommandFunction commandFunction3 : functions) { ResourceLocation resourceLocation3 = commandFunction3.id(); CommandResultCallback commandResultCallback5 = decorateOutputIfNeeded(originalSource, callbacks, resourceLocation3, commandResultCallback4); instantiateAndQueueFunctions( arguments, executionControl, commandDispatcher, executionCommandSource, commandFunction3, resourceLocation3, commandResultCallback5, false ); } executionControl.queueNext((executionContext, frame) -> { if (lv.anyResult) { commandResultCallback.onSuccess(lv.sum); } }); } } } public interface Callbacks { void signalResult(T source, ResourceLocation function, int commands); } abstract static class FunctionCustomExecutor extends CustomCommandExecutor.WithErrorHandling implements CustomCommandExecutor.CommandAdapter { @Nullable protected abstract CompoundTag arguments(CommandContext context) throws CommandSyntaxException; public void runGuarded( CommandSourceStack commandSourceStack, ContextChain contextChain, ChainModifiers chainModifiers, ExecutionControl executionControl ) throws CommandSyntaxException { CommandContext commandContext = contextChain.getTopContext().copyFor(commandSourceStack); Pair>> pair = FunctionArgument.getFunctionCollection(commandContext, "name"); Collection> collection = pair.getSecond(); if (collection.isEmpty()) { throw FunctionCommand.ERROR_NO_FUNCTIONS.create(Component.translationArg(pair.getFirst())); } else { CompoundTag compoundTag = this.arguments(commandContext); CommandSourceStack commandSourceStack2 = FunctionCommand.modifySenderForExecution(commandSourceStack); if (collection.size() == 1) { commandSourceStack.sendSuccess( () -> Component.translatable("commands.function.scheduled.single", Component.translationArg(((CommandFunction)collection.iterator().next()).id())), true ); } else { commandSourceStack.sendSuccess( () -> Component.translatable( "commands.function.scheduled.multiple", ComponentUtils.formatList(collection.stream().map(CommandFunction::id).toList(), Component::translationArg) ), true ); } FunctionCommand.queueFunctions( collection, compoundTag, commandSourceStack, commandSourceStack2, executionControl, FunctionCommand.FULL_CONTEXT_CALLBACKS, chainModifiers ); } } } }