diff --git a/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/FabricClientExample.java b/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/FabricClientExample.java index 940100a9..09d95bfb 100644 --- a/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/FabricClientExample.java +++ b/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/FabricClientExample.java @@ -23,26 +23,106 @@ // package cloud.commandframework.fabric.testmod; +import cloud.commandframework.Command; import cloud.commandframework.arguments.standard.StringArgument; import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.fabric.FabricClientCommandManager; +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.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v1.FabricClientCommandSource; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.SaveLevelScreen; +import net.minecraft.client.gui.screen.TitleScreen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.realms.gui.screen.RealmsBridgeScreen; +import net.minecraft.command.CommandSource; +import net.minecraft.command.argument.ArgumentTypes; +import net.minecraft.text.ClickEvent; import net.minecraft.text.LiteralText; +import net.minecraft.text.TranslatableText; +import org.checkerframework.checker.nullness.qual.NonNull; + +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 FabricClientExample implements ClientModInitializer { + @Override public void onInitializeClient() { final FabricClientCommandManager commandManager = FabricClientCommandManager.createNative(CommandExecutionCoordinator.simpleCoordinator()); - commandManager.command( - commandManager.commandBuilder("cloud_client") - .literal("say") - .argument(StringArgument.greedy("message")) - .handler(ctx -> ctx.getSender().sendFeedback( - new LiteralText("Cloud client commands says: " + ctx.get("message")) - )) - ); + final Command.Builder base = commandManager.commandBuilder("cloud_client"); + + commandManager.command(base.literal("dump") + .meta(CommandMeta.DESCRIPTION, "Dump the client's Brigadier command tree") + .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() + )))) + ); + + 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() + )); + } + })); + + commandManager.command(base.literal("say") + .argument(StringArgument.greedy("message")) + .handler(ctx -> ctx.getSender().sendFeedback( + new LiteralText("Cloud client commands says: " + ctx.get("message")) + ))); + + commandManager.command(base.literal("quit") + .handler(ctx -> { + final MinecraftClient client = MinecraftClient.getInstance(); + disconnectClient(client); + client.scheduleStop(); + })); + + commandManager.command(base.literal("disconnect") + .handler(ctx -> disconnectClient(MinecraftClient.getInstance()))); } + + private static void disconnectClient(final @NonNull MinecraftClient client) { + boolean singlePlayer = client.isInSingleplayer(); + client.world.disconnect(); + if (singlePlayer) { + client.disconnect(new SaveLevelScreen(new TranslatableText("menu.savingLevel"))); + } else { + client.disconnect(); + } + if (singlePlayer) { + client.openScreen(new TitleScreen()); + } else if (client.isConnectedToRealms()) { + new RealmsBridgeScreen().switchToRealms(new TitleScreen()); + } else { + client.openScreen(new MultiplayerScreen(new TitleScreen())); + } + } + } 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 38721844..83917195 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 @@ -26,112 +26,197 @@ package cloud.commandframework.fabric.testmod; import cloud.commandframework.Command; import cloud.commandframework.arguments.CommandArgument; +import cloud.commandframework.arguments.parser.ArgumentParseResult; import cloud.commandframework.arguments.standard.IntegerArgument; import cloud.commandframework.arguments.standard.StringArgument; import cloud.commandframework.execution.CommandExecutionCoordinator; import cloud.commandframework.fabric.FabricServerCommandManager; +import cloud.commandframework.fabric.argument.ColorArgument; +import cloud.commandframework.fabric.argument.ItemDataArgument; import cloud.commandframework.fabric.argument.server.MultiplePlayerSelectorArgument; import cloud.commandframework.fabric.data.MultiplePlayerSelector; -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 cloud.commandframework.fabric.testmod.mixin.GiveCommandAccess; 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.fabricmc.loader.api.ModContainer; +import net.fabricmc.loader.api.metadata.ModMetadata; +import net.fabricmc.loader.api.metadata.Person; +import net.minecraft.command.argument.ItemStackArgument; import net.minecraft.network.MessageType; -import net.minecraft.server.command.CommandManager; import net.minecraft.server.command.ServerCommandSource; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.ClickEvent; +import net.minecraft.text.HoverEvent; import net.minecraft.text.LiteralText; +import net.minecraft.text.MutableText; import net.minecraft.text.TextColor; +import net.minecraft.util.Formatting; import net.minecraft.util.Util; -import java.io.BufferedWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Instant; import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; 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(); - private static final CommandArgument PLAYER_SELECTOR = - MultiplePlayerSelectorArgument.of("players"); @Override public void onInitialize() { // Create a commands manager. We'll use native command source types for this. - final FabricServerCommandManager manager = FabricServerCommandManager.createNative(CommandExecutionCoordinator.simpleCoordinator()); final Command.Builder base = manager.commandBuilder("cloudtest"); + final CommandArgument name = StringArgument.of("name"); + final CommandArgument hugs = IntegerArgument.newBuilder("hugs") + .asOptionalWithDefault("1") + .build(); + manager.command(base - .argument(NAME) - .argument(HUGS) + .literal("hugs") + .argument(name) + .argument(hugs) .handler(ctx -> { ctx.getSender().sendFeedback(new LiteralText("Hello, ") - .append(ctx.get(NAME)) + .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))) + .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); })); - manager.command(base.literal("dump") - .meta(CommandMeta.DESCRIPTION, "Dump the client's Brigadier command tree (integrated server only)") - .meta(FabricServerCommandManager.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())); - } - })); + final CommandArgument playerSelector = + MultiplePlayerSelectorArgument.of("players"); + final CommandArgument textColor = ColorArgument.of("color"); manager.command(base.literal("wave") - .argument(PLAYER_SELECTOR) + .argument(playerSelector) + .argument(textColor) .handler(ctx -> { - final MultiplePlayerSelector selector = ctx.get(PLAYER_SELECTOR); + final MultiplePlayerSelector selector = ctx.get(playerSelector); final Collection selected = selector.get(); selected.forEach(selectedPlayer -> - selectedPlayer.sendMessage(new LiteralText("Wave"), MessageType.SYSTEM, Util.NIL_UUID)); - ctx.getSender().sendFeedback(new LiteralText(String.format("Waved at %d players (%s)", selected.size(), - selector.getInput())), - false); + selectedPlayer.sendMessage( + new LiteralText("Wave from ") + .styled(style -> style.withColor(ctx.get(textColor))) + .append(ctx.getSender().getDisplayName()), + MessageType.SYSTEM, + Util.NIL_UUID + )); + ctx.getSender().sendFeedback( + new LiteralText(String.format("Waved at %d players (%s)", selected.size(), + selector.getInput() + )), + false + ); })); + manager.command(base.literal("give") + .permission("cloud.give") + .argument(MultiplePlayerSelectorArgument.of("targets")) + .argument(ItemDataArgument.of("item")) + .argument(IntegerArgument.newBuilder("amount") + .withMin(1) + .asOptionalWithDefault("1")) + .handler(ctx -> { + final ItemStackArgument item = ctx.get("item"); + final MultiplePlayerSelector targets = ctx.get("targets"); + final int amount = ctx.get("amount"); + GiveCommandAccess.give( + ctx.getSender(), + item, + targets.get(), + amount + ); + })); + + final Command.Builder mods = base.literal("mods").permission("cloud.mods"); + + manager.command(mods.handler(ctx -> { + final List modList = FabricLoader.getInstance().getAllMods().stream() + .map(ModContainer::getMetadata) + .sorted(Comparator.comparing(ModMetadata::getId)) + .collect(Collectors.toList()); + final LiteralText text = new LiteralText(""); + text.append(new LiteralText("Loaded Mods") + .styled(style -> style.withColor(Formatting.BLUE).withFormatting(Formatting.BOLD))); + text.append(new LiteralText(String.format(" (%s)\n", modList.size())) + .styled(style -> style.withColor(Formatting.GRAY).withFormatting(Formatting.ITALIC))); + for (final ModMetadata mod : modList) { + text.append( + new LiteralText("") + .styled(style -> style.withColor(Formatting.WHITE) + .withClickEvent(new ClickEvent( + ClickEvent.Action.SUGGEST_COMMAND, + String.format("/cloudtest mods %s", mod.getId()) + )) + .withHoverEvent(new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + new LiteralText("Click for more info") + ))) + .append(new LiteralText(mod.getName()).styled(style -> style.withColor(Formatting.GREEN))) + .append(new LiteralText(String.format(" (%s) ", mod.getId())) + .styled(style -> style.withColor(Formatting.GRAY).withFormatting(Formatting.ITALIC))) + .append(new LiteralText(String.format("v%s", mod.getVersion()))) + ); + if (modList.indexOf(mod) != modList.size() - 1) { + text.append(new LiteralText(", ").styled(style -> style.withColor(Formatting.GRAY))); + } + } + ctx.getSender().sendFeedback(text, false); + })); + + final CommandArgument modMetadata = manager.argumentBuilder(ModMetadata.class, "mod") + .withSuggestionsProvider((ctx, input) -> FabricLoader.getInstance().getAllMods().stream() + .map(ModContainer::getMetadata) + .map(ModMetadata::getId) + .collect(Collectors.toList())) + .withParser((ctx, inputQueue) -> { + final ModMetadata meta = FabricLoader.getInstance().getModContainer(inputQueue.peek()) + .map(ModContainer::getMetadata) + .orElse(null); + if (meta != null) { + inputQueue.remove(); + return ArgumentParseResult.success(meta); + } + return ArgumentParseResult.failure(new IllegalArgumentException(String.format( + "No mod with id '%s'", + inputQueue.peek() + ))); + }) + .build(); + + manager.command(mods.argument(modMetadata) + .handler(ctx -> { + final ModMetadata meta = ctx.get(modMetadata); + final MutableText text = new LiteralText("") + .append(new LiteralText(meta.getName()) + .styled(style -> style.withColor(Formatting.BLUE).withFormatting(Formatting.BOLD))) + .append(new LiteralText("\n modid: " + meta.getId())) + .append(new LiteralText("\n version: " + meta.getVersion())) + .append(new LiteralText("\n type: " + meta.getType())); + + if (!meta.getDescription().isEmpty()) { + text.append(new LiteralText("\n description: " + meta.getDescription())); + } + if (!meta.getAuthors().isEmpty()) { + text.append(new LiteralText("\n authors: " + meta.getAuthors().stream() + .map(Person::getName) + .collect(Collectors.joining(", ")))); + } + if (!meta.getLicense().isEmpty()) { + text.append(new LiteralText("\n license: " + String.join(", ", meta.getLicense()))); + } + ctx.getSender().sendFeedback( + text, + false + ); + })); } } diff --git a/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/mixin/GiveCommandAccess.java b/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/mixin/GiveCommandAccess.java new file mode 100644 index 00000000..fe69f08b --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/testmod/java/cloud/commandframework/fabric/testmod/mixin/GiveCommandAccess.java @@ -0,0 +1,49 @@ +// +// MIT License +// +// Copyright (c) 2021 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.mixin; + +import com.mojang.brigadier.Command; +import net.minecraft.command.argument.ItemStackArgument; +import net.minecraft.server.command.GiveCommand; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.util.Collection; + +@Mixin(GiveCommand.class) +public interface GiveCommandAccess { + + @Invoker("execute") + static int give( + final ServerCommandSource source, + final ItemStackArgument item, + final Collection targets, + final int count + ) { + return Command.SINGLE_SUCCESS; + } + +} diff --git a/cloud-minecraft/cloud-fabric/src/testmod/resources/cloud-testmod.mixins.json b/cloud-minecraft/cloud-fabric/src/testmod/resources/cloud-testmod.mixins.json new file mode 100644 index 00000000..5ccb497e --- /dev/null +++ b/cloud-minecraft/cloud-fabric/src/testmod/resources/cloud-testmod.mixins.json @@ -0,0 +1,11 @@ +{ + "package": "cloud.commandframework.fabric.testmod.mixin", + "compatibilityLevel": "JAVA_8", + "required": true, + "mixins": [ + "GiveCommandAccess" + ], + "injectors": { + "defaultRequire": 1 + } +} 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 bb29107c..5b1a3f02 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": [ "Citomonstret", "zml" ], + "authors": [ "Citymonstret", "zml" ], "contact": { "homepage": "https://commandframework.cloud/", "sources": "https://github.com/Incendo/cloud" @@ -22,6 +22,10 @@ ] }, + "mixins": [ + "cloud-testmod.mixins.json" + ], + "depends": { "fabricloader": ">=0.7.4", "fabric-command-api-v1": "*",