fabric: Split out server-specific command manager to allow for client commands

This commit is contained in:
Zach Levis 2021-01-03 19:02:48 -08:00 committed by Jason
parent 6b690811f1
commit 48181164b0
5 changed files with 273 additions and 128 deletions

View file

@ -37,6 +37,7 @@ import cloud.commandframework.meta.CommandMeta;
import cloud.commandframework.meta.SimpleCommandMeta;
import com.mojang.brigadier.arguments.ArgumentType;
import io.leangen.geantyref.TypeToken;
import net.minecraft.command.CommandSource;
import net.minecraft.command.argument.AngleArgumentType;
import net.minecraft.command.argument.BlockPredicateArgumentType;
import net.minecraft.command.argument.ColorArgumentType;
@ -61,48 +62,34 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.particle.ParticleEffect;
import net.minecraft.predicate.NumberRange;
import net.minecraft.scoreboard.ScoreboardCriterion;
import net.minecraft.server.command.CommandManager.RegistrationEnvironment;
import net.minecraft.server.command.CommandOutput;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.LiteralText;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.Vec2f;
import net.minecraft.util.math.Vec3d;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.EnumSet;
import java.util.function.Function;
public class FabricCommandManager<C> extends CommandManager<C> implements BrigadierManagerHolder<C> {
import java.util.function.Supplier;
/**
* A meta attribute specifying which environments a command should be registered in.
* A command manager for either the server or client on Fabric.
*
* <p>The default value is {@link RegistrationEnvironment#ALL}.</p>
*/
public static final CommandMeta.Key<RegistrationEnvironment> META_REGISTRATION_ENVIRONMENT = CommandMeta.Key.of(
RegistrationEnvironment.class,
"cloud:registration-environment"
);
private final Function<ServerCommandSource, C> commandSourceMapper;
private final Function<C, ServerCommandSource> backwardsCommandSourceMapper;
private final CloudBrigadierManager<C, ServerCommandSource> brigadierManager;
/**
* Create a command manager using native source types.
* <p>Commands registered with managers of this type will be registered into a Brigadier command tree.</p>
*
* @param execCoordinator Execution coordinator instance.
* @return a new command manager
* @see #FabricCommandManager(Function, Function, Function) for a more thorough explanation
* <p>Where possible, Vanilla argument types are made available in a cloud-friendly format. In some cases, these argument
* types may only be available for server commands. Mod-provided argument types can be exposed to Cloud as well, by using
* {@link WrappedBrigadierParser}.</p>
*
* @param <C> the manager's sender type
* @param <S> the platform sender type
* @see FabricServerCommandManager for server commands
*/
public static FabricCommandManager<ServerCommandSource> createNative(
final Function<CommandTree<ServerCommandSource>, CommandExecutionCoordinator<ServerCommandSource>> execCoordinator
) {
return new FabricCommandManager<>(execCoordinator, Function.identity(), Function.identity());
}
public abstract class FabricCommandManager<C, S extends CommandSource> extends CommandManager<C> implements BrigadierManagerHolder<C> {
private final Function<S, C> commandSourceMapper;
private final Function<C, S> backwardsCommandSourceMapper;
private final CloudBrigadierManager<C, S> brigadierManager;
/**
@ -118,14 +105,18 @@ public class FabricCommandManager<C> extends CommandManager<C> implements Brigad
* {@link AsynchronousCommandExecutionCoordinator}
* @param commandSourceMapper Function that maps {@link ServerCommandSource} to the command sender type
* @param backwardsCommandSourceMapper Function that maps the command sender type to {@link ServerCommandSource}
* @param registrationHandler the handler accepting command registrations
* @param dummyCommandSourceProvider a provider of a dummy command source, for use with brigadier registration
*/
@SuppressWarnings("unchecked")
protected FabricCommandManager(
FabricCommandManager(
final @NonNull Function<@NonNull CommandTree<C>, @NonNull CommandExecutionCoordinator<C>> commandExecutionCoordinator,
final Function<ServerCommandSource, C> commandSourceMapper,
final Function<C, ServerCommandSource> backwardsCommandSourceMapper
final Function<S, C> commandSourceMapper,
final Function<C, S> backwardsCommandSourceMapper,
final FabricCommandRegistrationHandler<C, S> registrationHandler,
final Supplier<S> dummyCommandSourceProvider
) {
super(commandExecutionCoordinator, new FabricCommandRegistrationHandler<>());
super(commandExecutionCoordinator, registrationHandler);
this.commandSourceMapper = commandSourceMapper;
this.backwardsCommandSourceMapper = backwardsCommandSourceMapper;
@ -133,26 +124,16 @@ public class FabricCommandManager<C> extends CommandManager<C> implements Brigad
this.brigadierManager = new CloudBrigadierManager<>(this, () -> new CommandContext<>(
// This looks ugly, but it's what the server does when loading datapack functions in 1.16+
// See net.minecraft.server.function.FunctionLoader.reload for reference
this.commandSourceMapper.apply(new ServerCommandSource(
CommandOutput.DUMMY,
Vec3d.ZERO,
Vec2f.ZERO,
null,
4,
"",
LiteralText.EMPTY,
null,
null
)),
this.commandSourceMapper.apply(dummyCommandSourceProvider.get()),
this
));
this.brigadierManager.backwardsBrigadierSenderMapper(this.backwardsCommandSourceMapper);
this.registerNativeBrigadierMappings(this.brigadierManager);
((FabricCommandRegistrationHandler<C>) this.getCommandRegistrationHandler()).initialize(this);
((FabricCommandRegistrationHandler<C, S>) this.getCommandRegistrationHandler()).initialize(this);
}
private void registerNativeBrigadierMappings(final CloudBrigadierManager<C, ServerCommandSource> brigadier) {
private void registerNativeBrigadierMappings(final CloudBrigadierManager<C, S> brigadier) {
/* Cloud-native argument types */
brigadier.registerMapping(new TypeToken<UUIDArgument.UUIDParser<C>>() {}, false, cloud -> UuidArgumentType.uuid());
@ -200,27 +181,26 @@ public class FabricCommandManager<C> extends CommandManager<C> implements Brigad
// entity argument type: single or multiple, players or any entity -- returns EntitySelector, but do we want that?
}
private <T> void registerConstantNativeParserSupplier(final Class<T> type, final ArgumentType<T> argument) {
/**
* Register a parser supplier for a brigadier type that has no options and whose output can be directly used.
*
* @param type the Java type to map
* @param argument the Brigadier parser
* @param <T> value type
*/
final <T> void registerConstantNativeParserSupplier(final Class<T> type, final ArgumentType<T> argument) {
this.registerConstantNativeParserSupplier(TypeToken.get(type), argument);
}
private <T> void registerConstantNativeParserSupplier(final TypeToken<T> type, final ArgumentType<T> argument) {
this.getParserRegistry().registerParserSupplier(type, params -> new WrappedBrigadierParser<>(argument));
}
/**
* Check if a sender has a certain permission.
* Register a parser supplier for a brigadier type that has no options and whose output can be directly used.
*
* <p>The current implementation checks op level, pending a full Fabric permissions api.</p>
*
* @param sender Command sender
* @param permission Permission node
* @return whether the sender has the specified permission
* @param type the Java type to map
* @param argument the Brigadier parser
* @param <T> value type
*/
@Override
public boolean hasPermission(@NonNull final C sender, @NonNull final String permission) {
final ServerCommandSource source = this.backwardsCommandSourceMapper.apply(sender);
return source.hasPermissionLevel(source.getMinecraftServer().getOpPermissionLevel());
final <T> void registerConstantNativeParserSupplier(final TypeToken<T> type, final ArgumentType<T> argument) {
this.getParserRegistry().registerParserSupplier(type, params -> new WrappedBrigadierParser<>(argument));
}
@Override
@ -233,7 +213,7 @@ public class FabricCommandManager<C> extends CommandManager<C> implements Brigad
*
* @return Command source mapper
*/
public final @NonNull Function<@NonNull ServerCommandSource, @NonNull C> getCommandSourceMapper() {
public final @NonNull Function<@NonNull S, @NonNull C> getCommandSourceMapper() {
return this.commandSourceMapper;
}
@ -242,12 +222,12 @@ public class FabricCommandManager<C> extends CommandManager<C> implements Brigad
*
* @return Command source mapper
*/
public final @NonNull Function<@NonNull C, @NonNull ServerCommandSource> getBackwardsCommandSourceMapper() {
public final @NonNull Function<@NonNull C, @NonNull S> getBackwardsCommandSourceMapper() {
return this.backwardsCommandSourceMapper;
}
@Override
public final @NonNull CloudBrigadierManager<C, ServerCommandSource> brigadierManager() {
public final @NonNull CloudBrigadierManager<C, S> brigadierManager() {
return this.brigadierManager;
}

View file

@ -31,6 +31,7 @@ import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.command.CommandSource;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.CommandManager.RegistrationEnvironment;
import net.minecraft.server.command.ServerCommandSource;
@ -43,17 +44,28 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* A registration handler for Fabric API.
*
* <p>If the command registration callback has already been called, this will attempt
* to register with the active server's command dispatcher.</p>
* <p>Subtypes exist for client and server commands.</p>
*
* @param <C> command sender type
* @param <S> native sender type
*/
public final class FabricCommandRegistrationHandler<C> implements CommandRegistrationHandler {
private @MonotonicNonNull FabricCommandManager<C> commandManager;
abstract class FabricCommandRegistrationHandler<C, S extends CommandSource> implements CommandRegistrationHandler {
private @MonotonicNonNull FabricCommandManager<C, S> commandManager;
void initialize(final FabricCommandManager<C, S> manager) {
this.commandManager = manager;
}
FabricCommandManager<C, S> getCommandManager() {
return this.commandManager;
}
static class Server<C> extends FabricCommandRegistrationHandler<C, ServerCommandSource> {
private final Set<Command<C>> registeredCommands = ConcurrentHashMap.newKeySet();
void initialize(final FabricCommandManager<C> manager) {
this.commandManager = manager;
@Override
void initialize(final FabricCommandManager<C, ServerCommandSource> manager) {
super.initialize(manager);
CommandRegistrationCallback.EVENT.register(this::registerAllCommands);
}
@ -64,11 +76,11 @@ public final class FabricCommandRegistrationHandler<C> implements CommandRegistr
}
private void registerAllCommands(final CommandDispatcher<ServerCommandSource> dispatcher, final boolean isDedicated) {
this.commandManager.registrationCalled();
this.getCommandManager().registrationCalled();
for (final Command<C> command : this.registeredCommands) {
/* Only register commands in the declared environment */
final RegistrationEnvironment env = command.getCommandMeta().getOrDefault(
FabricCommandManager.META_REGISTRATION_ENVIRONMENT,
FabricServerCommandManager.META_REGISTRATION_ENVIRONMENT,
RegistrationEnvironment.ALL
);
@ -83,12 +95,15 @@ public final class FabricCommandRegistrationHandler<C> implements CommandRegistr
private void registerCommand(final RootCommandNode<ServerCommandSource> dispatcher, final Command<C> command) {
@SuppressWarnings("unchecked")
final StaticArgument<C> first = ((StaticArgument<C>) command.getArguments().get(0));
final CommandNode<ServerCommandSource> baseNode = this.commandManager.brigadierManager().createLiteralCommandNode(
final CommandNode<ServerCommandSource> baseNode = this.getCommandManager().brigadierManager().createLiteralCommandNode(
first.getName(),
command,
(src, perm) -> this.commandManager.hasPermission(this.commandManager.getCommandSourceMapper().apply(src), perm),
(src, perm) -> this.getCommandManager().hasPermission(
this.getCommandManager().getCommandSourceMapper().apply(src),
perm
),
true,
new FabricExecutor<>(this.commandManager));
new FabricExecutor<>(this.getCommandManager(), ServerCommandSource::getName, ServerCommandSource::sendError));
dispatcher.addChild(baseNode);
@ -96,4 +111,6 @@ public final class FabricCommandRegistrationHandler<C> implements CommandRegistr
dispatcher.addChild(CommandManager.literal(alias).redirect(baseNode).build());
}
}
}
}

View file

@ -34,7 +34,7 @@ import cloud.commandframework.execution.CommandResult;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.command.CommandSource;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.HoverEvent;
import net.minecraft.text.LiteralText;
@ -50,8 +50,9 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.CompletionException;
import java.util.function.BiConsumer;
import java.util.function.Function;
final class FabricExecutor<C> implements Command<ServerCommandSource> {
final class FabricExecutor<C, S extends CommandSource> implements Command<S> {
private static final Logger LOGGER = LogManager.getLogger();
private static final Text NEWLINE = new LiteralText("\n");
@ -61,15 +62,23 @@ final class FabricExecutor<C> implements Command<ServerCommandSource> {
+ "Please contact the server administrators if you believe that this is in error.";
private static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command. Type \"/help\" for help.";
private final FabricCommandManager<C> manager;
private final FabricCommandManager<C, S> manager;
private final Function<S, String> getName;
private final BiConsumer<S, Text> sendError;
FabricExecutor(final @NonNull FabricCommandManager<C> manager) {
FabricExecutor(
final @NonNull FabricCommandManager<C, S> manager,
final @NonNull Function<S, String> getName,
final @NonNull BiConsumer<S, Text> sendError
) {
this.manager = manager;
this.getName = getName;
this.sendError = sendError;
}
@Override
public int run(final @NonNull CommandContext<ServerCommandSource> ctx) {
final ServerCommandSource source = ctx.getSource();
public int run(final @NonNull CommandContext<S> ctx) {
final S source = ctx.getSource();
final String input = ctx.getInput().substring(1);
final C sender = this.manager.getCommandSourceMapper().apply(source);
this.manager.executeCommand(sender, input).whenComplete(this.getResultConsumer(source, sender));
@ -77,7 +86,7 @@ final class FabricExecutor<C> implements Command<ServerCommandSource> {
}
private @NonNull BiConsumer<@NonNull CommandResult<C>, ? super Throwable> getResultConsumer(
final @NonNull ServerCommandSource source,
final @NonNull S source,
final @NonNull C sender
) {
return (result, throwable) -> {
@ -92,7 +101,8 @@ final class FabricExecutor<C> implements Command<ServerCommandSource> {
InvalidSyntaxException.class,
(InvalidSyntaxException) throwable,
(c, e) ->
source.sendError(
this.sendError.accept(
source,
new LiteralText("Invalid Command Syntax. Correct command syntax is: ")
.append(new LiteralText(e.getCorrectSyntax())
.styled(style -> style.withColor(Formatting.GRAY))))
@ -103,21 +113,21 @@ final class FabricExecutor<C> implements Command<ServerCommandSource> {
InvalidCommandSenderException.class,
(InvalidCommandSenderException) throwable,
(c, e) ->
source.sendError(new LiteralText(finalThrowable.getMessage()))
this.sendError.accept(source, new LiteralText(finalThrowable.getMessage()))
);
} else if (throwable instanceof NoPermissionException) {
this.manager.handleException(
sender,
NoPermissionException.class,
(NoPermissionException) throwable,
(c, e) -> source.sendError(new LiteralText(MESSAGE_NO_PERMS))
(c, e) -> this.sendError.accept(source, new LiteralText(MESSAGE_NO_PERMS))
);
} else if (throwable instanceof NoSuchCommandException) {
this.manager.handleException(
sender,
NoSuchCommandException.class,
(NoSuchCommandException) throwable,
(c, e) -> source.sendError(new LiteralText(MESSAGE_UNKNOWN_COMMAND))
(c, e) -> this.sendError.accept(source, new LiteralText(MESSAGE_UNKNOWN_COMMAND))
);
} else if (throwable instanceof ArgumentParseException) {
this.manager.handleException(
@ -126,12 +136,12 @@ final class FabricExecutor<C> implements Command<ServerCommandSource> {
(ArgumentParseException) throwable,
(c, e) -> {
if (finalThrowable.getCause() instanceof CommandSyntaxException) {
source.sendError(new LiteralText("Invalid Command Argument: ")
this.sendError.accept(source, new LiteralText("Invalid Command Argument: ")
.append(new LiteralText("")
.append(Texts.toText(((CommandSyntaxException) finalThrowable.getCause()).getRawMessage()))
.formatted(Formatting.GRAY)));
} else {
source.sendError(new LiteralText("Invalid Command Argument: ")
this.sendError.accept(source, new LiteralText("Invalid Command Argument: ")
.append(new LiteralText(finalThrowable.getCause().getMessage())
.formatted(Formatting.GRAY)));
}
@ -143,21 +153,29 @@ final class FabricExecutor<C> implements Command<ServerCommandSource> {
CommandExecutionException.class,
(CommandExecutionException) throwable,
(c, e) -> {
source.sendError(this.decorateHoverStacktrace(
this.sendError.accept(source, this.decorateHoverStacktrace(
new LiteralText(MESSAGE_INTERNAL_ERROR),
finalThrowable.getCause(),
sender
));
LOGGER.warn("Error occurred while executing command for user {}:", source.getName(), finalThrowable);
LOGGER.warn(
"Error occurred while executing command for user {}:",
this.getName.apply(source),
finalThrowable
);
}
);
} else {
source.sendError(this.decorateHoverStacktrace(
this.sendError.accept(source, this.decorateHoverStacktrace(
new LiteralText(MESSAGE_INTERNAL_ERROR),
throwable,
sender
));
LOGGER.warn("Error occurred while executing command for user {}:", source.getName(), throwable);
LOGGER.warn(
"Error occurred while executing command for user {}:",
this.getName.apply(source),
throwable
);
}
}
};

View file

@ -0,0 +1,130 @@
//
// MIT License
//
// Copyright (c) 2020 Alexander Söderberg & Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
package cloud.commandframework.fabric;
import cloud.commandframework.CommandTree;
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.meta.CommandMeta;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.CommandOutput;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.LiteralText;
import net.minecraft.util.math.Vec2f;
import net.minecraft.util.math.Vec3d;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.function.Function;
/**
* A command manager for registering server-side commands.
*
* <p>All commands should be registered within mod initializers. Any registrations occurring after the first call to
* {@link CommandRegistrationCallback} will be considered <em>unsafe</em>, and will only be permitted when the unsafe
* registration manager option is enabled.</p>
*
* @param <C> the command sender type
* @since 1.4.0
*/
public final class FabricServerCommandManager<C> extends FabricCommandManager<C, ServerCommandSource> {
/**
* A meta attribute specifying which environments a command should be registered in.
*
* <p>The default value is {@link CommandManager.RegistrationEnvironment#ALL}.</p>
*/
public static final CommandMeta.Key<CommandManager.RegistrationEnvironment> META_REGISTRATION_ENVIRONMENT = CommandMeta.Key.of(
CommandManager.RegistrationEnvironment.class,
"cloud:registration-environment"
);
/**
* Create a command manager using native source types.
*
* @param execCoordinator Execution coordinator instance.
* @return a new command manager
* @see #FabricServerCommandManager(Function, Function, Function) for a more thorough explanation
*/
public static FabricServerCommandManager<ServerCommandSource> createNative(
final Function<CommandTree<ServerCommandSource>, CommandExecutionCoordinator<ServerCommandSource>> execCoordinator
) {
return new FabricServerCommandManager<>(execCoordinator, Function.identity(), Function.identity());
}
/**
* Create a new command manager instance.
*
* @param commandExecutionCoordinator Execution coordinator instance. The coordinator is in charge of executing incoming
* commands. Some considerations must be made when picking a suitable execution coordinator
* for your platform. For example, an entirely asynchronous coordinator is not suitable
* when the parsers used in that particular platform are not thread safe. If you have
* commands that perform blocking operations, however, it might not be a good idea to
* use a synchronous execution coordinator. In most cases you will want to pick between
* {@link CommandExecutionCoordinator#simpleCoordinator()} and
* {@link AsynchronousCommandExecutionCoordinator}
* @param commandSourceMapper Function that maps {@link ServerCommandSource} to the command sender type
* @param backwardsCommandSourceMapper Function that maps the command sender type to {@link ServerCommandSource}
*/
public FabricServerCommandManager(
final @NonNull Function<@NonNull CommandTree<C>, @NonNull CommandExecutionCoordinator<C>> commandExecutionCoordinator,
final Function<ServerCommandSource, C> commandSourceMapper,
final Function<C, ServerCommandSource> backwardsCommandSourceMapper
) {
super(
commandExecutionCoordinator,
commandSourceMapper,
backwardsCommandSourceMapper,
new FabricCommandRegistrationHandler.Server<>(),
() -> new ServerCommandSource(
CommandOutput.DUMMY,
Vec3d.ZERO,
Vec2f.ZERO,
null,
4,
"",
LiteralText.EMPTY,
null,
null
)
);
}
/**
* Check if a sender has a certain permission.
*
* <p>The current implementation checks op level, pending a full Fabric permissions api.</p>
*
* @param sender Command sender
* @param permission Permission node
* @return whether the sender has the specified permission
*/
@Override
public boolean hasPermission(@NonNull final C sender, @NonNull final String permission) {
final ServerCommandSource source = this.getBackwardsCommandSourceMapper().apply(sender);
return source.hasPermissionLevel(source.getMinecraftServer().getOpPermissionLevel());
}
}

View file

@ -29,7 +29,7 @@ import cloud.commandframework.arguments.CommandArgument;
import cloud.commandframework.arguments.standard.IntegerArgument;
import cloud.commandframework.arguments.standard.StringArgument;
import cloud.commandframework.execution.CommandExecutionCoordinator;
import cloud.commandframework.fabric.FabricCommandManager;
import cloud.commandframework.fabric.FabricServerCommandManager;
import cloud.commandframework.meta.CommandMeta;
import com.google.gson.JsonObject;
import com.google.gson.internal.Streams;
@ -62,8 +62,8 @@ public final class FabricExample implements ModInitializer {
public void onInitialize() {
// Create a commands manager. We'll use native command source types for this.
final FabricCommandManager<ServerCommandSource> manager =
FabricCommandManager.createNative(CommandExecutionCoordinator.simpleCoordinator());
final FabricServerCommandManager<ServerCommandSource> manager =
FabricServerCommandManager.createNative(CommandExecutionCoordinator.simpleCoordinator());
final Command.Builder<ServerCommandSource> base = manager.commandBuilder("cloudtest");
@ -85,7 +85,7 @@ public final class FabricExample implements ModInitializer {
manager.command(base.literal("dump")
.meta(CommandMeta.DESCRIPTION, "Dump the client's Brigadier command tree (integrated server only)")
.meta(FabricCommandManager.META_REGISTRATION_ENVIRONMENT, CommandManager.RegistrationEnvironment.INTEGRATED)
.meta(FabricServerCommandManager.META_REGISTRATION_ENVIRONMENT, CommandManager.RegistrationEnvironment.INTEGRATED)
.handler(ctx -> {
final Path target =
FabricLoader.getInstance().getGameDir().resolve(