fabric: Begin adding support for some wrapped vanilla arguments

This commit is contained in:
Zach Levis 2021-01-03 14:25:41 -08:00 committed by Jason
parent 62caa2d641
commit 3be50956cc
13 changed files with 453 additions and 27 deletions

View file

@ -1,8 +1,10 @@
import net.ltgt.gradle.errorprone.errorprone
plugins {
id("fabric-loom") version "0.5-SNAPSHOT"
}
// Set up a testmod source set
/* set up a testmod source set */
val testmod by sourceSets.creating {
val main = sourceSets.main.get()
compileClasspath += main.compileClasspath
@ -18,30 +20,41 @@ val testmodJar by tasks.creating(Jar::class) {
loom.unmappedModCollection.from(testmodJar)
tasks.withType(ProcessResources::class).configureEach {
inputs.property("version", project.version)
filesMatching("fabric.mod.json") {
expand("version" to project.version)
/* end of testmod setup */
tasks {
compileJava {
options.errorprone {
excludedPaths.set(".*[/\\\\]mixin[/\\\\].*")
}
}
withType(ProcessResources::class).configureEach {
inputs.property("version", project.version)
filesMatching("fabric.mod.json") {
expand("version" to project.version)
}
}
withType(Javadoc::class).configureEach {
(options as? StandardJavadocDocletOptions)?.apply {
links("https://maven.fabricmc.net/docs/yarn-${Versions.fabricMc}+build.${Versions.fabricYarn}/")
}
}
}
tasks.withType(Javadoc::class).configureEach {
(options as? StandardJavadocDocletOptions)?.apply {
links("https://maven.fabricmc.net/docs/yarn-${Versions.fabricMc}+build.${Versions.fabricYarn}/")
}
}
dependencies {
minecraft("com.mojang:minecraft:${Versions.fabricMc}")
mappings("net.fabricmc:yarn:${Versions.fabricMc}+build.${Versions.fabricYarn}:v2")
modImplementation("net.fabricmc:fabric-loader:${Versions.fabricLoader}")
minecraft("com.mojang", "minecraft", Versions.fabricMc)
mappings("net.fabricmc", "yarn", "${Versions.fabricMc}+build.${Versions.fabricYarn}", classifier = "v2")
modImplementation("net.fabricmc", "fabric-loader", Versions.fabricLoader)
modImplementation(fabricApi.module("fabric-command-api-v1", Versions.fabricApi))
api(include(project(":cloud-core"))!!)
implementation(include(project(":cloud-brigadier"))!!)
include(project(":cloud-services"))
include("io.leangen.geantyref:geantyref:${Versions.geantyref}")
include("io.leangen.geantyref", "geantyref", Versions.geantyref)
}
indra {

View file

@ -26,21 +26,53 @@ package cloud.commandframework.fabric;
import cloud.commandframework.CommandManager;
import cloud.commandframework.CommandTree;
import cloud.commandframework.arguments.standard.UUIDArgument;
import cloud.commandframework.brigadier.BrigadierManagerHolder;
import cloud.commandframework.brigadier.CloudBrigadierManager;
import cloud.commandframework.brigadier.argument.WrappedBrigadierParser;
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator;
import cloud.commandframework.execution.CommandExecutionCoordinator;
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.argument.AngleArgumentType;
import net.minecraft.command.argument.BlockPredicateArgumentType;
import net.minecraft.command.argument.ColorArgumentType;
import net.minecraft.command.argument.EntityAnchorArgumentType;
import net.minecraft.command.argument.IdentifierArgumentType;
import net.minecraft.command.argument.ItemEnchantmentArgumentType;
import net.minecraft.command.argument.ItemStackArgument;
import net.minecraft.command.argument.ItemStackArgumentType;
import net.minecraft.command.argument.MessageArgumentType;
import net.minecraft.command.argument.MobEffectArgumentType;
import net.minecraft.command.argument.NbtCompoundTagArgumentType;
import net.minecraft.command.argument.NbtPathArgumentType;
import net.minecraft.command.argument.NumberRangeArgumentType;
import net.minecraft.command.argument.ObjectiveCriteriaArgumentType;
import net.minecraft.command.argument.OperationArgumentType;
import net.minecraft.command.argument.ParticleArgumentType;
import net.minecraft.command.argument.SwizzleArgumentType;
import net.minecraft.command.argument.UuidArgumentType;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.entity.effect.StatusEffect;
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> {
@ -114,10 +146,68 @@ public class FabricCommandManager<C> extends CommandManager<C> implements Brigad
)),
this
));
this.brigadierManager.backwardsBrigadierSenderMapper(this.backwardsCommandSourceMapper);
this.registerNativeBrigadierMappings(this.brigadierManager);
((FabricCommandRegistrationHandler<C>) this.getCommandRegistrationHandler()).initialize(this);
}
private void registerNativeBrigadierMappings(final CloudBrigadierManager<C, ServerCommandSource> brigadier) {
/* Cloud-native argument types */
brigadier.registerMapping(new TypeToken<UUIDArgument.UUIDParser<C>>() {}, false, cloud -> UuidArgumentType.uuid());
/* Wrapped/Constant Brigadier types, native value type */
this.registerConstantNativeParserSupplier(Formatting.class, ColorArgumentType.color());
this.registerConstantNativeParserSupplier(BlockPredicateArgumentType.BlockPredicate.class,
BlockPredicateArgumentType.blockPredicate());
this.registerConstantNativeParserSupplier(MessageArgumentType.MessageFormat.class, MessageArgumentType.message());
this.registerConstantNativeParserSupplier(CompoundTag.class, NbtCompoundTagArgumentType.nbtCompound());
this.registerConstantNativeParserSupplier(NbtPathArgumentType.NbtPath.class, NbtPathArgumentType.nbtPath());
this.registerConstantNativeParserSupplier(ScoreboardCriterion.class, ObjectiveCriteriaArgumentType.objectiveCriteria());
this.registerConstantNativeParserSupplier(OperationArgumentType.Operation.class, OperationArgumentType.operation());
this.registerConstantNativeParserSupplier(ParticleEffect.class, ParticleArgumentType.particle());
this.registerConstantNativeParserSupplier(AngleArgumentType.Angle.class, AngleArgumentType.angle());
this.registerConstantNativeParserSupplier(new TypeToken<EnumSet<Direction.Axis>>() {}, SwizzleArgumentType.swizzle());
this.registerConstantNativeParserSupplier(Identifier.class, IdentifierArgumentType.identifier());
this.registerConstantNativeParserSupplier(StatusEffect.class, MobEffectArgumentType.mobEffect());
this.registerConstantNativeParserSupplier(EntityAnchorArgumentType.EntityAnchor.class, EntityAnchorArgumentType.entityAnchor());
this.registerConstantNativeParserSupplier(NumberRange.IntRange.class, NumberRangeArgumentType.numberRange());
this.registerConstantNativeParserSupplier(NumberRange.FloatRange.class, NumberRangeArgumentType.method_30918());
this.registerConstantNativeParserSupplier(Enchantment.class, ItemEnchantmentArgumentType.itemEnchantment());
// todo: can we add a compound argument -- MC `ItemStackArgument` is just type and tag, and count is separate
this.registerConstantNativeParserSupplier(ItemStackArgument.class, ItemStackArgumentType.itemStack());
/* Wrapped/Constant Brigadier types, mapped value type */
/*this.registerConstantNativeParserSupplier(GameProfile.class, GameProfileArgumentType.gameProfile());
this.registerConstantNativeParserSupplier(BlockPos.class, BlockPosArgumentType.blockPos());
this.registerConstantNativeParserSupplier(ColumnPos.class, ColumnPosArgumentType.columnPos());
this.registerConstantNativeParserSupplier(Vec3d.class, Vec3ArgumentType.vec3());
this.registerConstantNativeParserSupplier(Vec2f.class, Vec2ArgumentType.vec2());
this.registerConstantNativeParserSupplier(BlockState.class, BlockStateArgumentType.blockState());
this.registerConstantNativeParserSupplier(ItemPredicate.class, ItemPredicateArgumentType.itemPredicate());
this.registerConstantNativeParserSupplier(ScoreboardObjective.class, ObjectiveArgumentType.objective());
this.registerConstantNativeParserSupplier(PosArgument.class, RotationArgumentType.rotation()); // todo: different type
this.registerConstantNativeParserSupplier(ScoreboardSlotArgumentType.scoreboardSlot());
this.registerConstantNativeParserSupplier(Team.class, TeamArgumentType.team());
this.registerConstantNativeParserSupplier(/* slot *, ItemSlotArgumentType.itemSlot());
this.registerConstantNativeParserSupplier(CommandFunction.class, FunctionArgumentType.function());
this.registerConstantNativeParserSupplier(EntityType.class, EntitySummonArgumentType.entitySummon()); // entity summon
this.registerConstantNativeParserSupplier(ServerWorld.class, DimensionArgumentType.dimension());
this.registerConstantNativeParserSupplier(/* time representation in ticks *, TimeArgumentType.time());*/
/* Wrapped brigadier requiring parameters */
// score holder: single vs multiple
// 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) {
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.
*

View file

@ -33,13 +33,17 @@ import cloud.commandframework.exceptions.NoSuchCommandException;
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.text.ClickEvent;
import net.minecraft.text.HoverEvent;
import net.minecraft.text.LiteralText;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.minecraft.text.Texts;
import net.minecraft.util.Formatting;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.io.PrintWriter;
@ -48,6 +52,8 @@ import java.util.concurrent.CompletionException;
import java.util.function.BiConsumer;
final class FabricExecutor<C> implements Command<ServerCommandSource> {
private static final Logger LOGGER = LogManager.getLogger();
private static final Text NEWLINE = new LiteralText("\n");
private static final String MESSAGE_INTERNAL_ERROR = "An internal error occurred while attempting to perform this command.";
private static final String MESSAGE_NO_PERMS =
@ -118,9 +124,18 @@ final class FabricExecutor<C> implements Command<ServerCommandSource> {
sender,
ArgumentParseException.class,
(ArgumentParseException) throwable,
(c, e) -> source.sendError(new LiteralText("Invalid Command Argument: ")
.append(new LiteralText(finalThrowable.getCause().getMessage())
.styled(style -> style.withColor(Formatting.GRAY))))
(c, e) -> {
if (finalThrowable.getCause() instanceof CommandSyntaxException) {
source.sendError(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: ")
.append(new LiteralText(finalThrowable.getCause().getMessage())
.formatted(Formatting.GRAY)));
}
}
);
} else if (throwable instanceof CommandExecutionException) {
this.manager.handleException(
@ -128,21 +143,21 @@ final class FabricExecutor<C> implements Command<ServerCommandSource> {
CommandExecutionException.class,
(CommandExecutionException) throwable,
(c, e) -> {
source.sendError(decorateHoverStacktrace(
source.sendError(this.decorateHoverStacktrace(
new LiteralText(MESSAGE_INTERNAL_ERROR),
finalThrowable.getCause(),
sender
));
finalThrowable.getCause().printStackTrace();
LOGGER.warn("Error occurred while executing command for user {}:", source.getName(), finalThrowable);
}
);
} else {
source.sendError(decorateHoverStacktrace(
source.sendError(this.decorateHoverStacktrace(
new LiteralText(MESSAGE_INTERNAL_ERROR),
throwable,
sender
));
throwable.printStackTrace();
LOGGER.warn("Error occurred while executing command for user {}:", source.getName(), throwable);
}
}
};

View file

@ -0,0 +1,45 @@
//
// 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.internal;
import com.mojang.brigadier.StringReader;
/**
* An extension to the Brigadier StringReader that also implements Queue (via mixin).
*
* @see cloud.commandframework.fabric.mixin.CloudStringReaderMixin for the {@link java.util.Queue} implementation
*/
public final class CloudStringReader extends StringReader {
/**
* Create a new reader from text.
*
* @param input the input
*/
public CloudStringReader(final String input) {
super(input);
}
}

View file

@ -0,0 +1,28 @@
//
// 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.
//
/**
* Implementation-internal content for the Fabric implementation of Cloud.
*/
package cloud.commandframework.fabric.internal;

View file

@ -0,0 +1,123 @@
//
// 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.mixin;
import cloud.commandframework.brigadier.argument.StringReaderAsQueue;
import cloud.commandframework.fabric.internal.CloudStringReader;
import com.mojang.brigadier.StringReader;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import java.util.Objects;
/**
* Mix in to our own class in order to implement the Queue interface without signature conflicts.
*
* <p>This must be kept in sync with the wrapping implementation in {@code cloud-brigadier}</p>
*/
@Mixin(value = CloudStringReader.class, remap = false)
public class CloudStringReaderMixin implements StringReaderAsQueue {
private int cloud$nextSpaceIdx; /* the character before the start of a new word */
private @Nullable String cloud$nextWord;
/* Next whitespace index starting at startIdx, or -1 if none is found */
static int cloud$nextWhitespace(final String input, final int startIdx) {
for (int i = startIdx, length = input.length(); i < length; ++i) {
if (Character.isWhitespace(input.charAt(i))) {
return i;
}
}
return -1;
}
@Override
public StringReader getOriginal() {
return (StringReader) (Object) this;
}
/* Brigadier doesn't automatically consume whitespace... in order to get the matched behaviour, we consume whitespace
* after every popped string.
*/
private void cloud$advance() {
final int startOfNextWord = this.cloud$nextSpaceIdx + 1;
this.cloud$nextSpaceIdx = cloud$nextWhitespace(((StringReader) (Object) this).getString(), startOfNextWord);
if (this.cloud$nextSpaceIdx != -1) {
this.cloud$nextWord = ((StringReader) (Object) this).getString().substring(startOfNextWord, this.cloud$nextSpaceIdx);
} else if (startOfNextWord < ((StringReader) (Object) this).getTotalLength()) {
this.cloud$nextWord = ((StringReader) (Object) this).getString().substring(startOfNextWord);
this.cloud$nextSpaceIdx = ((StringReader) (Object) this).getTotalLength() + 1;
} else {
this.cloud$nextWord = null;
}
((StringReader) (Object) this).setCursor(startOfNextWord);
}
@Override
public String poll() {
/* peek and then advance */
final String next = this.peek();
if (next != null) {
this.cloud$advance();
}
return next;
}
@Override
public String peek() {
return this.cloud$nextWord;
}
@Override
public int size() {
if (this.cloud$nextWord == null) {
return 0;
}
int counter = 1;
for (int i = this.cloud$nextSpaceIdx;
i != -1 && i < ((StringReader) (Object) this).getTotalLength();
i = cloud$nextWhitespace(((StringReader) (Object) this).getString(), i + 1)) {
counter++;
}
return counter;
}
@Override
public boolean remove(final Object o) {
if (Objects.equals(o, this.cloud$nextWord)) {
this.cloud$advance();
return true;
}
return false;
}
@Override
public void clear() {
StringReaderAsQueue.super.clear();
this.cloud$nextWord = null;
this.cloud$nextSpaceIdx = -1;
}
}

View file

@ -0,0 +1,43 @@
//
// 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.mixin;
import cloud.commandframework.fabric.internal.CloudStringReader;
import com.mojang.brigadier.StringReader;
import net.minecraft.server.command.CommandManager;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
@Mixin(CommandManager.class)
public class CommandManagerMixin {
/* Use our StringReader. This is technically optional, but it's nicer to avoid re-wrapping the object every time */
@Redirect(method = "execute", at = @At(value = "NEW", target = "com/mojang/brigadier/StringReader", remap = false), require = 0)
private StringReader cloud$newStringReader(final String arguments) {
return new CloudStringReader(arguments);
}
}

View file

@ -0,0 +1,12 @@
{
"package": "cloud.commandframework.fabric.mixin",
"compatibilityLevel": "JAVA_8",
"required": true,
"mixins": [
"CloudStringReaderMixin",
"CommandManagerMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -5,7 +5,7 @@
"name": "Cloud",
"description": "Command framework and dispatcher for the JVM",
"authors": [ "Alexander Söderberg" ],
"authors": [ "Citomonstret", "zml" ],
"contact": {
"homepage": "https://commandframework.cloud/",
"sources": "https://github.com/Incendo/cloud"

View file

@ -24,16 +24,34 @@
package cloud.commandframework.fabric.testmod;
import cloud.commandframework.Command;
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.meta.CommandMeta;
import com.google.gson.JsonObject;
import com.google.gson.internal.Streams;
import com.google.gson.stream.JsonWriter;
import com.mojang.brigadier.CommandDispatcher;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.MinecraftClient;
import net.minecraft.command.CommandSource;
import net.minecraft.command.argument.ArgumentTypes;
import net.minecraft.server.command.CommandManager;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.ClickEvent;
import net.minecraft.text.LiteralText;
import net.minecraft.text.TextColor;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
public final class FabricExample implements ModInitializer {
private static final CommandArgument<ServerCommandSource, String> NAME = StringArgument.of("name");
private static final CommandArgument<ServerCommandSource, Integer> HUGS = IntegerArgument.<ServerCommandSource>newBuilder("hugs")
@ -47,7 +65,9 @@ public final class FabricExample implements ModInitializer {
final FabricCommandManager<ServerCommandSource> manager =
FabricCommandManager.createNative(CommandExecutionCoordinator.simpleCoordinator());
manager.command(manager.commandBuilder("cloudtest")
final Command.Builder<ServerCommandSource> base = manager.commandBuilder("cloudtest");
manager.command(base
.argument(NAME)
.argument(HUGS)
.handler(ctx -> {
@ -63,6 +83,37 @@ public final class FabricExample implements ModInitializer {
.styled(style -> style.withBold(true)), false);
}));
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)
.handler(ctx -> {
final Path target =
FabricLoader.getInstance().getGameDir().resolve(
"cloud-dump-"
+ Instant.now().toString().replace(':', '-')
+ ".json"
);
ctx.getSender().sendFeedback(new LiteralText("Dumping command output to ")
.append(new LiteralText(target.toString())
.styled(s -> s.withClickEvent(new ClickEvent(
ClickEvent.Action.OPEN_FILE,
target.toAbsolutePath().toString()
)))), false);
try (BufferedWriter writer = Files.newBufferedWriter(target); JsonWriter json = new JsonWriter(writer)) {
final CommandDispatcher<CommandSource> dispatcher = MinecraftClient.getInstance()
.getNetworkHandler()
.getCommandDispatcher();
final JsonObject object = ArgumentTypes.toJson(dispatcher, dispatcher.getRoot());
json.setIndent(" ");
Streams.write(object, json);
} catch (final IOException ex) {
ctx.getSender().sendError(new LiteralText("Unable to write file, see console for details: " + ex.getMessage()));
}
}));
}
}

View file

@ -5,7 +5,7 @@
"name": "Cloud Test mod",
"description": "Command framework and dispatcher for the JVM",
"authors": [ "Alexander Söderberg" ],
"authors": [ "Citomonstret", "zml" ],
"contact": {
"homepage": "https://commandframework.cloud/",
"sources": "https://github.com/Incendo/cloud"