diff --git a/.checkstyle/checkstyle-suppressions.xml b/.checkstyle/checkstyle-suppressions.xml new file mode 100644 index 00000000..dcf53640 --- /dev/null +++ b/.checkstyle/checkstyle-suppressions.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/.checkstyle/checkstyle.xml b/.checkstyle/checkstyle.xml index 2c6abd29..c4d2ecce 100644 --- a/.checkstyle/checkstyle.xml +++ b/.checkstyle/checkstyle.xml @@ -78,9 +78,8 @@ - - + + diff --git a/cloud-minecraft/cloud-fabric/build.gradle.kts b/cloud-minecraft/cloud-fabric/build.gradle.kts index d394a6a9..495a1205 100644 --- a/cloud-minecraft/cloud-fabric/build.gradle.kts +++ b/cloud-minecraft/cloud-fabric/build.gradle.kts @@ -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 { diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandManager.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandManager.java index f3944475..cf6e7c35 100644 --- a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandManager.java +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandManager.java @@ -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 extends CommandManager implements BrigadierManagerHolder { @@ -114,10 +146,68 @@ public class FabricCommandManager extends CommandManager implements Brigad )), this )); + this.brigadierManager.backwardsBrigadierSenderMapper(this.backwardsCommandSourceMapper); + this.registerNativeBrigadierMappings(this.brigadierManager); ((FabricCommandRegistrationHandler) this.getCommandRegistrationHandler()).initialize(this); } + private void registerNativeBrigadierMappings(final CloudBrigadierManager brigadier) { + /* Cloud-native argument types */ + brigadier.registerMapping(new TypeToken>() {}, 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>() {}, 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 void registerConstantNativeParserSupplier(final Class type, final ArgumentType argument) { + this.registerConstantNativeParserSupplier(TypeToken.get(type), argument); + } + + private void registerConstantNativeParserSupplier(final TypeToken type, final ArgumentType argument) { + this.getParserRegistry().registerParserSupplier(type, params -> new WrappedBrigadierParser<>(argument)); + } + /** * Check if a sender has a certain permission. * diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricExecutor.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricExecutor.java index e561b8a5..ea529b24 100644 --- a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricExecutor.java +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricExecutor.java @@ -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 implements Command { + 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 implements Command { 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 implements Command { 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); } } }; diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/internal/CloudStringReader.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/internal/CloudStringReader.java new file mode 100644 index 00000000..9673ec2f --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/internal/CloudStringReader.java @@ -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); + } + +} diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/internal/package-info.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/internal/package-info.java new file mode 100644 index 00000000..e877b932 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/internal/package-info.java @@ -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; diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/mixin/CloudStringReaderMixin.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/mixin/CloudStringReaderMixin.java new file mode 100644 index 00000000..9c5d4617 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/mixin/CloudStringReaderMixin.java @@ -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. + * + *

This must be kept in sync with the wrapping implementation in {@code cloud-brigadier}

+ */ +@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; + } +} diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/mixin/CommandManagerMixin.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/mixin/CommandManagerMixin.java new file mode 100644 index 00000000..49b37976 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/mixin/CommandManagerMixin.java @@ -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); + } + +} diff --git a/cloud-minecraft/cloud-fabric/src/main/resources/cloud.mixins.json b/cloud-minecraft/cloud-fabric/src/main/resources/cloud.mixins.json new file mode 100644 index 00000000..e7d1ae12 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/resources/cloud.mixins.json @@ -0,0 +1,12 @@ +{ + "package": "cloud.commandframework.fabric.mixin", + "compatibilityLevel": "JAVA_8", + "required": true, + "mixins": [ + "CloudStringReaderMixin", + "CommandManagerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/cloud-minecraft/cloud-fabric/src/main/resources/fabric.mod.json b/cloud-minecraft/cloud-fabric/src/main/resources/fabric.mod.json index b332db0a..de9d621f 100644 --- a/cloud-minecraft/cloud-fabric/src/main/resources/fabric.mod.json +++ b/cloud-minecraft/cloud-fabric/src/main/resources/fabric.mod.json @@ -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" diff --git a/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/FabricExample.java b/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/FabricExample.java index f255161f..a4403bc8 100644 --- a/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/FabricExample.java +++ b/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/FabricExample.java @@ -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 NAME = StringArgument.of("name"); private static final CommandArgument HUGS = IntegerArgument.newBuilder("hugs") @@ -47,7 +65,9 @@ public final class FabricExample implements ModInitializer { final FabricCommandManager manager = FabricCommandManager.createNative(CommandExecutionCoordinator.simpleCoordinator()); - manager.command(manager.commandBuilder("cloudtest") + final Command.Builder 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 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())); + } + })); + + + } } diff --git a/cloud-minecraft/cloud-fabric/src/testmod/resources/fabric.mod.json b/cloud-minecraft/cloud-fabric/src/testmod/resources/fabric.mod.json index f923a9dd..5396ad26 100644 --- a/cloud-minecraft/cloud-fabric/src/testmod/resources/fabric.mod.json +++ b/cloud-minecraft/cloud-fabric/src/testmod/resources/fabric.mod.json @@ -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"