diff --git a/.gitignore b/.gitignore index 9d64cf5a..740c5580 100644 --- a/.gitignore +++ b/.gitignore @@ -222,3 +222,7 @@ gradle-app.setting **/build/ # End of https://www.toptal.com/developers/gitignore/api/git,java,eclipse,jetbrains+all,gradle + +### Fabric Loom run files ### + +/run/ diff --git a/README.md b/README.md index 5694cb75..b574bafb 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ The code is based on a (W.I.P) paper that can be found [here](https://github.com - **cloud-minecraft/cloud-bungee**: BungeeCord 1.8.8+ implementation of Cloud - **cloud-minecraft/cloud-velocity**: Velocity v1.1.0 implementation of cloud - **cloud-minecraft/cloud-cloudburst**: Cloudburst v1.0.0+ implementation of cloud +- **cloud-minecraft/cloud-fabric**: Fabric implementation of Cloud - **cloud-minecraft/cloud-minecraft-extras**: Opinionated Extra Features for cloud-minecraft - **cloud-discord/cloud-jda**: JDA v4.2.0_209+ implementation of cloud - **cloud-discord/cloud-javacord**: Javacord v3.1.1+ implementation of cloud diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 2dd289a6..ccb02050 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -23,6 +23,10 @@ object Versions { const val spongeApi7 = "7.3.0" const val jetbrainsAnnotations = "20.1.0" const val guava = "21.0-jre" + const val fabricLoader = "0.10.8" + const val fabricMc = "1.16.4" + const val fabricYarn = "7" + const val fabricApi = "0.29.1+1.16" // IRC DEPENDENCIES const val pircbotx = "83a4c22e80" diff --git a/cloud-minecraft/README.md b/cloud-minecraft/README.md index 5ca12894..e42e3d7a 100644 --- a/cloud-minecraft/README.md +++ b/cloud-minecraft/README.md @@ -9,12 +9,14 @@ This directory hosts Minecraft specific implementations of cloud. Their features | `cloud-bungee` | BungeeCord | 1.8+ | No | | `cloud-velocity` | Velocity 1.1.0 | 1.7+ | Yes | | `cloud-cloudburst` | CloudBurst 1.0.0 | Bedrock 1.16.20+ | No | +| `cloud-fabric` | Minecraft, via Fabric | 1.16+ | Yes | There is also a `cloud-minecraft-extras` module that contains a few extra minecraft related features ## cloud-bukkit -Bukkit mappings for cloud. If `commodore` is present on the classpath and the server is running at least version 1.13+, Brigadier mappings will be available. +Bukkit mappings for cloud. If `commodore` is present on the classpath and the server is running at least version 1.13+, Brigadier +mappings will be available. ### dependency **maven**: @@ -39,7 +41,10 @@ Simply do: final BukkitCommandManager bukkitCommandManager = new BukkitCommandManager<>( yourPlugin, yourExecutionCoordinator, forwardMapper, backwardsMapper); ``` -The `forwardMapper` is a function that maps your chosen sender type to Bukkit's [CommandSender](https://jd.bukkit.org/org/bukkit/command/CommandSender.html), and the `backwardsMapper`does the opposite. In the case that you don't need a custom sender type, you can simply use `CommandSender`as the generic type and pass `Function.identity()` as the forward and backward mappers. +The `forwardMapper` is a function that maps your chosen sender type to Bukkit's +[CommandSender](https://jd.bukkit.org/org/bukkit/command/CommandSender.html), and the `backwardsMapper` does the opposite. In + the case that you don't need a custom sender type, you can simply use `CommandSender`as the generic type + and pass `Function.identity()` as the forward and backward mappers. ### commodore To use commodore, include it as a dependency: @@ -78,7 +83,9 @@ You can check whether or not the running server supports Brigadier, by using `bu An example plugin using the `cloud-paper` API can be found [here](https://github.com/Sauilitired/cloud/tree/master/examples/example-bukkit). -`cloud-paper`works on all Bukkit derivatives and has graceful fallbacks for cases where Paper specific features are missing. It is initialized the same way as the Bukkit manager, except `PaperCommandManager`is used instead. When using Paper 1.15+ Brigadier mappings are available even without commodore present. +`cloud-paper`works on all Bukkit derivatives and has graceful fallbacks for cases where Paper specific features are missing. It +is initialized the same way as the Bukkit manager, except `PaperCommandManager`is used instead. When using Paper 1.15+ Brigadier +mappings are available even without commodore present. ### dependency **maven**: @@ -98,7 +105,9 @@ dependencies { ``` ### asynchronous completions -`cloud-paper`supports asynchronous completions when running on Paper. First check if the capability is present, by using `paperCommandManager.queryCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)` and then initialize the asynchronous completion listener by using `paperCommandManager.registerAsynchronousCompletions()`. +`cloud-paper`supports asynchronous completions when running on Paper. First check if the capability is present, by using +`paperCommandManager.queryCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)` and then initialize the asynchronous +completion listener by using `paperCommandManager.registerAsynchronousCompletions()`. ## cloud-bungee BungeeCord mappings for cloud. @@ -126,7 +135,10 @@ Simply do: final BungeeCommandManager bungeeCommandManager = new BungeeCommandManager<>( yourPlugin, yourExecutionCoordinator, forwardMapper, backwardsMapper); ``` -The `forwardMapper` is a function that maps your chosen sender type to Bungee's [CommandSender](https://ci.md-5.net/job/BungeeCord/ws/api/target/apidocs/net/md_5/bungee/api/CommandSender.html), and the `backwardsMapper`does the opposite. In the case that you don't need a custom sender type, you can simply use `CommandSender`as the generic type and pass `Function.identity()` as the forward and backward mappers. +The `forwardMapper` is a function that maps your chosen sender type to Bungee's +[CommandSender](https://ci.md-5.net/job/BungeeCord/ws/api/target/apidocs/net/md_5/bungee/api/CommandSender.html), and +the `backwardsMapper`does the opposite. In the case that you don't need a custom sender type, you can simply use `CommandSender`as + the generic type and pass `Function.identity()` as the forward and backward mappers. ## cloud-velocity @@ -154,7 +166,10 @@ Simply do: final VelocityCommandManager velocityCommandManager = new VelocityCommandManager<>( proxyServer, yourExecutionCoordinator, forwardMapper, backwardsMapper); ``` -The `forwardMapper` is a function that maps your chosen sender type to Velocity's [CommandSource](https://jd.velocitypowered.com/1.1.0/com/velocitypowered/api/command/CommandSource.html), and the `backwardsMapper`does the opposite. In the case that you don't need a custom sender type, you can simply use `CommandSource`as the generic type and pass `Function.identity()` as the forward and backward mappers. +The `forwardMapper` is a function that maps your chosen sender type to Velocity's +[CommandSource](https://jd.velocitypowered.com/1.1.0/com/velocitypowered/api/command/CommandSource.html), and the +`backwardsMapper` does the opposite. In the case that you don't need a custom sender type, you can simply use `CommandSource`as +the generic type and pass `Function.identity()` as the forward and backward mappers. ## cloud-cloudburst @@ -182,4 +197,35 @@ Simply do: final CloudburstCommandManager cloudburstCommandManager = new CloudburstCommandManager<>( yourPlugin, yourExecutionCoordinator, forwardMapper, backwardsMapper); ``` -The `forwardMapper` is a function that maps your chosen sender type to Cloudbursts's [CommandSender](https://ci.nukkitx.com/job/NukkitX/job/Nukkit/job/master/javadoc/cn/nukkit/command/CommandSender.html), and the `backwardsMapper`does the opposite. In the case that you don't need a custom sender type, you can simply use `CommandSource`as the generic type and pass `Function.identity()` as the forward and backward mappers. +The `forwardMapper` is a function that maps your chosen sender type to Cloudbursts's +[CommandSender](https://ci.nukkitx.com/job/NukkitX/job/Nukkit/job/master/javadoc/cn/nukkit/command/CommandSender.html), and the +`backwardsMapper` does the opposite. In the case that you don't need a custom sender type, you can simply use `CommandSource` as +the generic type and pass `Function.identity()` as the forward and backward mappers. + + +## cloud-fabric + +cloud mappings for the Fabric mod loader for Minecraft 1.16+ + +### dependency + +**gradle**: +```groovy +dependencies { + modImplementation 'cloud.commandframework:cloud-fabric:1.3.0-SNAPSHOT' +} +``` + +Simply do: +```java +final FabricCommandManager fabricCommandManager = new FabricCommandManager<>( + yourExecutionCoordinator, forwarMapper, backwardsMapper); +``` + +The `forwardMapper` is a function that maps your chosen sender type to Minecraft's +[ServerCommandSource](https://maven.fabricmc.net/docs/yarn-1.16.4+build.7/net/minecraft/server/command/ServerCommandSource.html), +and the `backwardsMapper` does the opposite. + +In the case that you don't need a custom sender type, you can use the helper method +`FabricCommandManager.createNative(yourExecutionCoordinator)` instead, which will create a command manager that works directly + with `ServerCommandSource`s. diff --git a/cloud-minecraft/cloud-fabric/build.gradle.kts b/cloud-minecraft/cloud-fabric/build.gradle.kts new file mode 100644 index 00000000..d394a6a9 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/build.gradle.kts @@ -0,0 +1,73 @@ +plugins { + id("fabric-loom") version "0.5-SNAPSHOT" +} + +// Set up a testmod source set +val testmod by sourceSets.creating { + val main = sourceSets.main.get() + compileClasspath += main.compileClasspath + runtimeClasspath += main.runtimeClasspath + dependencies.add(implementationConfigurationName, main.output) +} + +val testmodJar by tasks.creating(Jar::class) { + archiveClassifier.set("testmod-dev") + group = LifecycleBasePlugin.BUILD_GROUP + from(testmod.output) +} + +loom.unmappedModCollection.from(testmodJar) + +tasks.withType(ProcessResources::class).configureEach { + inputs.property("version", project.version) + filesMatching("fabric.mod.json") { + expand("version" to project.version) + } +} + +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}") + 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}") +} + +indra { + includeJavaSoftwareComponentInPublications.set(false) + configurePublications { + // add all the jars that should be included when publishing to maven + artifact(tasks.remapJar) { + builtBy(tasks.remapJar) + } + artifact(tasks.sourcesJar) { + builtBy(tasks.remapSourcesJar) + } + + // Loom is broken with project dependencies in the same build (because it resolves dependencies during configuration) + // Please look away + pom { + withXml { + val dependencies = asNode().appendNode("dependencies") + sequenceOf("brigadier", "core").forEach { + val depNode = dependencies.appendNode("dependency") + depNode.appendNode("groupId", project.group) + depNode.appendNode("artifactId", "cloud-$it") + depNode.appendNode("version", project.version) + depNode.appendNode("scope", "compile") + } + } + } + } +} 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 new file mode 100644 index 00000000..cf5e5a78 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandManager.java @@ -0,0 +1,152 @@ +// +// 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.CommandManager; +import cloud.commandframework.CommandTree; +import cloud.commandframework.brigadier.BrigadierManagerHolder; +import cloud.commandframework.brigadier.CloudBrigadierManager; +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 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; + +public class FabricCommandManager extends CommandManager implements BrigadierManagerHolder { + private final Function commandSourceMapper; + private final Function backwardsCommandSourceMapper; + private final CloudBrigadierManager brigadierManager; + + /** + * Create a command manager using native source types. + * + * @param execCoordinator Execution coordinator instance. + * @return a new command manager + * @see #FabricCommandManager(Function, Function, Function) for a more thorough explanation + */ + public static FabricCommandManager createNative( + final Function, CommandExecutionCoordinator> execCoordinator + ) { + return new FabricCommandManager<>(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} + */ + @SuppressWarnings("unchecked") + protected FabricCommandManager( + final @NonNull Function<@NonNull CommandTree, @NonNull CommandExecutionCoordinator> commandExecutionCoordinator, + final Function commandSourceMapper, + final Function backwardsCommandSourceMapper + ) { + super(commandExecutionCoordinator, new FabricCommandRegistrationHandler<>()); + this.commandSourceMapper = commandSourceMapper; + this.backwardsCommandSourceMapper = backwardsCommandSourceMapper; + + // We're always brigadier + 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.getCaptionRegistry() + )); + + ((FabricCommandRegistrationHandler) this.getCommandRegistrationHandler()).initialize(this); + } + + /** + * Check if a sender has a certain permission. + * + *

The current implementation checks op level, pending a full Fabric permissions api.

+ * + * @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.backwardsCommandSourceMapper.apply(sender); + return source.hasPermissionLevel(source.getMinecraftServer().getOpPermissionLevel()); + } + + @Override + public final @NonNull CommandMeta createDefaultCommandMeta() { + return SimpleCommandMeta.empty(); + } + + /** + * Gets the mapper from a game {@link ServerCommandSource} to the manager's {@code C} type. + * + * @return Command source mapper + */ + public final @NonNull Function<@NonNull ServerCommandSource, @NonNull C> getCommandSourceMapper() { + return this.commandSourceMapper; + } + + /** + * Gets the mapper from the manager's {@code C} type to a game {@link ServerCommandSource}. + * + * @return Command source mapper + */ + public final @NonNull Function<@NonNull C, @NonNull ServerCommandSource> getBackwardsCommandSourceMapper() { + return this.backwardsCommandSourceMapper; + } + + @Override + public final @NonNull CloudBrigadierManager brigadierManager() { + return this.brigadierManager; + } + +} diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandRegistrationHandler.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandRegistrationHandler.java new file mode 100644 index 00000000..4191775b --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricCommandRegistrationHandler.java @@ -0,0 +1,92 @@ +// +// 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.Command; +import cloud.commandframework.arguments.StaticArgument; +import cloud.commandframework.internal.CommandRegistrationHandler; +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.server.command.CommandManager; +import net.minecraft.server.command.ServerCommandSource; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A registration handler for Fabric API. + * + *

If the command registration callback has already been called, this will attempt + * to register with the active server's command dispatcher.

+ * + * @param command sender type + */ +public final class FabricCommandRegistrationHandler implements CommandRegistrationHandler { + private @MonotonicNonNull FabricCommandManager commandManager; + private final Set> registeredCommands = ConcurrentHashMap.newKeySet(); + private boolean commandRegistrationCalled; + + void initialize(final FabricCommandManager manager) { + this.commandManager = manager; + CommandRegistrationCallback.EVENT.register(this::registerAllCommands); + } + + @Override + @SuppressWarnings("unchecked") + public boolean registerCommand(@NonNull final Command command) { + if (this.commandRegistrationCalled) { + throw new IllegalStateException("too late!"); + } + return this.registeredCommands.add((Command) command); + } + + private void registerAllCommands(final CommandDispatcher dispatcher, final boolean isDedicated) { + this.commandRegistrationCalled = true; + for (final Command command : this.registeredCommands) { + registerCommand(dispatcher.getRoot(), command); + } + } + + private void registerCommand(final RootCommandNode dispatcher, final Command command) { + @SuppressWarnings("unchecked") + final StaticArgument first = ((StaticArgument) command.getArguments().get(0)); + final CommandNode baseNode = this.commandManager.brigadierManager().createLiteralCommandNode( + first.getName(), + command, + (src, perm) -> this.commandManager.hasPermission(this.commandManager.getCommandSourceMapper().apply(src), perm), + true, + new FabricExecutor<>(this.commandManager)); + + dispatcher.addChild(baseNode); + + for (final String alias : first.getAlternativeAliases()) { + dispatcher.addChild(CommandManager.literal(alias).redirect(baseNode).build()); + } + } +} 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 new file mode 100644 index 00000000..e561b8a5 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/FabricExecutor.java @@ -0,0 +1,174 @@ +// +// 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.exceptions.ArgumentParseException; +import cloud.commandframework.exceptions.CommandExecutionException; +import cloud.commandframework.exceptions.InvalidCommandSenderException; +import cloud.commandframework.exceptions.InvalidSyntaxException; +import cloud.commandframework.exceptions.NoPermissionException; +import cloud.commandframework.exceptions.NoSuchCommandException; +import cloud.commandframework.execution.CommandResult; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +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.util.Formatting; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.concurrent.CompletionException; +import java.util.function.BiConsumer; + +final class FabricExecutor implements Command { + 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 = + "I'm sorry, but you do not have permission to perform this command. " + + "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 manager; + + FabricExecutor(final @NonNull FabricCommandManager manager) { + this.manager = manager; + } + + @Override + public int run(final @NonNull CommandContext ctx) { + final ServerCommandSource 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)); + return com.mojang.brigadier.Command.SINGLE_SUCCESS; + } + + private @NonNull BiConsumer<@NonNull CommandResult, ? super Throwable> getResultConsumer( + final @NonNull ServerCommandSource source, + final @NonNull C sender + ) { + return (result, throwable) -> { + if (throwable != null) { + if (throwable instanceof CompletionException) { + throwable = throwable.getCause(); + } + final Throwable finalThrowable = throwable; + if (throwable instanceof InvalidSyntaxException) { + this.manager.handleException( + sender, + InvalidSyntaxException.class, + (InvalidSyntaxException) throwable, + (c, e) -> + source.sendError( + new LiteralText("Invalid Command Syntax. Correct command syntax is: ") + .append(new LiteralText(e.getCorrectSyntax()) + .styled(style -> style.withColor(Formatting.GRAY)))) + ); + } else if (throwable instanceof InvalidCommandSenderException) { + this.manager.handleException( + sender, + InvalidCommandSenderException.class, + (InvalidCommandSenderException) throwable, + (c, e) -> + source.sendError(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)) + ); + } else if (throwable instanceof NoSuchCommandException) { + this.manager.handleException( + sender, + NoSuchCommandException.class, + (NoSuchCommandException) throwable, + (c, e) -> source.sendError(new LiteralText(MESSAGE_UNKNOWN_COMMAND)) + ); + } else if (throwable instanceof ArgumentParseException) { + this.manager.handleException( + 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)))) + ); + } else if (throwable instanceof CommandExecutionException) { + this.manager.handleException( + sender, + CommandExecutionException.class, + (CommandExecutionException) throwable, + (c, e) -> { + source.sendError(decorateHoverStacktrace( + new LiteralText(MESSAGE_INTERNAL_ERROR), + finalThrowable.getCause(), + sender + )); + finalThrowable.getCause().printStackTrace(); + } + ); + } else { + source.sendError(decorateHoverStacktrace( + new LiteralText(MESSAGE_INTERNAL_ERROR), + throwable, + sender + )); + throwable.printStackTrace(); + } + } + }; + } + + private MutableText decorateHoverStacktrace(final MutableText input, final Throwable cause, final C sender) { + if (!this.manager.hasPermission(sender, "cloud.hover-stacktrace")) { + return input; + } + + final StringWriter writer = new StringWriter(); + cause.printStackTrace(new PrintWriter(writer)); + final String stackTrace = writer.toString().replace("\t", " "); + return input.styled(style -> style + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new LiteralText(stackTrace) + .append(NEWLINE) + .append(new LiteralText(" Click to copy") + .styled(s2 -> s2 + .withColor(Formatting.GRAY) + .withItalic(true))) + )) + .withClickEvent(new ClickEvent( + ClickEvent.Action.COPY_TO_CLIPBOARD, + stackTrace + ))); + } + +} diff --git a/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/package-info.java b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/package-info.java new file mode 100644 index 00000000..a7cb2418 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/java/cloud/commandframework/fabric/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. +// + +/** + * Fabric API-based implementation of Cloud + */ +package cloud.commandframework.fabric; diff --git a/cloud-minecraft/cloud-fabric/src/main/resources/assets/cloud/logo.png b/cloud-minecraft/cloud-fabric/src/main/resources/assets/cloud/logo.png new file mode 100644 index 00000000..42acc718 Binary files /dev/null and b/cloud-minecraft/cloud-fabric/src/main/resources/assets/cloud/logo.png differ diff --git a/cloud-minecraft/cloud-fabric/src/main/resources/fabric.mod.json b/cloud-minecraft/cloud-fabric/src/main/resources/fabric.mod.json new file mode 100644 index 00000000..b332db0a --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/main/resources/fabric.mod.json @@ -0,0 +1,21 @@ +{ + "schemaVersion": 1, + "id": "cloud", + "version": "${version}", + + "name": "Cloud", + "description": "Command framework and dispatcher for the JVM", + "authors": [ "Alexander Söderberg" ], + "contact": { + "homepage": "https://commandframework.cloud/", + "sources": "https://github.com/Incendo/cloud" + }, + "license": "MIT", + "icon": "assets/cloud/logo.png", + + "depends": { + "fabricloader": ">=0.7.4", + "fabric-command-api-v1": "*", + "minecraft": ">=1.14" + } +} 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 new file mode 100644 index 00000000..f255161f --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/FabricExample.java @@ -0,0 +1,68 @@ +// +// 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.testmod; + +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 net.fabricmc.api.ModInitializer; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.LiteralText; +import net.minecraft.text.TextColor; + +public final class FabricExample implements ModInitializer { + private static final CommandArgument NAME = StringArgument.of("name"); + private static final CommandArgument HUGS = IntegerArgument.newBuilder("hugs") + .asOptionalWithDefault("1") + .build(); + + @Override + public void onInitialize() { + // Create a commands manager. We'll use native command source types for this. + + final FabricCommandManager manager = + FabricCommandManager.createNative(CommandExecutionCoordinator.simpleCoordinator()); + + manager.command(manager.commandBuilder("cloudtest") + .argument(NAME) + .argument(HUGS) + .handler(ctx -> { + ctx.getSender().sendFeedback(new LiteralText("Hello, ") + .append(ctx.get(NAME)) + .append(", hope you're doing well!") + .styled(style -> style.withColor(TextColor.fromRgb(0xAA22BB))), false); + + ctx.getSender().sendFeedback(new LiteralText("Cloud would like to give you ") + .append(new LiteralText(String.valueOf(ctx.get(HUGS))) + .styled(style -> style.withColor(TextColor.fromRgb(0xFAB3DA)))) + .append(" hug(s) <3") + .styled(style -> style.withBold(true)), false); + })); + + } + +} diff --git a/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/package-info.java b/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/package-info.java new file mode 100644 index 00000000..188f9307 --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/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. +// + +/** + * A test mod for the fabric implementation of Cloud. + */ +package cloud.commandframework.fabric.testmod; diff --git a/cloud-minecraft/cloud-fabric/src/testmod/resources/fabric.mod.json b/cloud-minecraft/cloud-fabric/src/testmod/resources/fabric.mod.json new file mode 100644 index 00000000..f923a9dd --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/testmod/resources/fabric.mod.json @@ -0,0 +1,28 @@ +{ + "schemaVersion": 1, + "id": "cloud-testmod", + "version": "${version}", + + "name": "Cloud Test mod", + "description": "Command framework and dispatcher for the JVM", + "authors": [ "Alexander Söderberg" ], + "contact": { + "homepage": "https://commandframework.cloud/", + "sources": "https://github.com/Incendo/cloud" + }, + "license": "MIT", + "icon": "assets/cloud/logo.png", + + "entrypoints": { + "main": [ + "cloud.commandframework.fabric.testmod.FabricExample" + ] + }, + + "depends": { + "fabricloader": ">=0.7.4", + "fabric-command-api-v1": "*", + "minecraft": ">=1.14", + "cloud": "*" + } +} diff --git a/gradle.properties b/gradle.properties index 3e3ee277..38464656 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ org.gradle.parallel=true -org.gradle.jvmargs=-Xmx512m +org.gradle.jvmargs=-Xmx1G diff --git a/settings.gradle.kts b/settings.gradle.kts index 63bbc266..44a910b8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,12 @@ +pluginManagement { + repositories { + maven("https://maven.fabricmc.net") { + name = "fabric" + } + gradlePluginPortal() + } +} + rootProject.name = "cloud" // Core Modules @@ -16,6 +25,7 @@ setupDiscordModule("cloud-jda") // Minecraft Modules setupMinecraftModule("cloud-brigadier") setupMinecraftModule("cloud-bukkit") +setupMinecraftModule("cloud-fabric") setupMinecraftModule("cloud-paper") setupMinecraftModule("cloud-velocity") setupMinecraftModule("cloud-sponge")