From df8bd9b58319ea38535906c991484b7593af4871 Mon Sep 17 00:00:00 2001 From: Camotoy <20743703+Camotoy@users.noreply.github.com> Date: Fri, 1 Nov 2024 00:39:24 -0400 Subject: [PATCH] Implement rich commands for Cloud on standalone --- .../geyser/command/CommandRegistry.java | 111 ++++++++++++++++++ .../protocol/java/JavaCommandsTranslator.java | 5 + 2 files changed, 116 insertions(+) diff --git a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java index 54681abea..78e8a5cb1 100644 --- a/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java +++ b/core/src/main/java/org/geysermc/geyser/command/CommandRegistry.java @@ -27,6 +27,13 @@ package org.geysermc.geyser.command; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import org.checkerframework.checker.nullness.qual.NonNull; +import org.cloudburstmc.protocol.bedrock.data.command.CommandData; +import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumConstraint; +import org.cloudburstmc.protocol.bedrock.data.command.CommandEnumData; +import org.cloudburstmc.protocol.bedrock.data.command.CommandOverloadData; +import org.cloudburstmc.protocol.bedrock.data.command.CommandParam; +import org.cloudburstmc.protocol.bedrock.data.command.CommandParamData; +import org.cloudburstmc.protocol.bedrock.data.command.CommandPermission; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.command.Command; import org.geysermc.geyser.api.event.EventRegistrar; @@ -51,14 +58,26 @@ import org.geysermc.geyser.command.defaults.StopCommand; import org.geysermc.geyser.command.defaults.VersionCommand; import org.geysermc.geyser.event.type.GeyserDefineCommandsEventImpl; import org.geysermc.geyser.extension.command.GeyserExtensionCommand; +import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; import org.incendo.cloud.Command.Builder; import org.incendo.cloud.CommandManager; import org.incendo.cloud.execution.ExecutionCoordinator; +import org.incendo.cloud.internal.CommandNode; +import org.incendo.cloud.parser.standard.EnumParser; +import org.incendo.cloud.parser.standard.IntegerParser; +import org.incendo.cloud.parser.standard.LiteralParser; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.Set; import static org.geysermc.geyser.command.GeyserCommand.DEFAULT_ROOT_COMMAND; @@ -299,4 +318,96 @@ public class CommandRegistry implements EventRegistrar { public void runCommand(@NonNull GeyserCommandSource source, @NonNull String command) { cloud.commandExecutor().executeCommand(source, command); } + + public void export(GeyserSession session, List bedrockCommands) { + cloud.commandTree().rootNode().children().forEach(commandTree -> { + var command = commandTree.command(); + // Command null happens if you register an extension command with custom Cloud parameters... + if (command == null || session.hasPermission(command.commandPermission().permissionString())) { + var rootComponent = commandTree.component(); + String name = rootComponent.name(); + + LinkedHashMap> values = new LinkedHashMap<>(); + for (String s : rootComponent.aliases()) { + values.put(s, EnumSet.of(CommandEnumConstraint.ALLOW_ALIASES)); + } + CommandEnumData aliases = new CommandEnumData(name + "Aliases", values, false); + + List data = new ArrayList<>(); + for (var node : commandTree.children()) { + List> params = new ArrayList<>(); + createParamData(node, params); + params.forEach(param -> data.add(new CommandOverloadData(false, param.toArray(CommandParamData[]::new)))); + } + + CommandData bedrockCommand = new CommandData(name, rootComponent.description().textDescription(), + Set.of(CommandData.Flag.NOT_CHEAT), CommandPermission.ANY, aliases, + Collections.emptyList(), data.toArray(new CommandOverloadData[0])); + bedrockCommands.add(bedrockCommand); + } + }); + } + + private void createParamData(CommandNode node, List> bedrockData) { + CommandParamData data = new CommandParamData(); + var component = node.component(); + data.setName(component.name()); + data.setOptional(component.optional()); + var suggestionProvider = component.suggestionProvider(); + if (suggestionProvider instanceof LiteralParser parser) { + Map> values = new LinkedHashMap<>(); + for (String alias : parser.aliases()) { + values.put(alias, Set.of()); + } + + data.setEnumData(new CommandEnumData(component.name(), values, false)); + } else if (suggestionProvider instanceof IntegerParser) { + data.setType(CommandParam.INT); + } else if (suggestionProvider instanceof EnumParser parser) { + LinkedHashMap> map = new LinkedHashMap<>(); + for (Enum e : parser.acceptedValues()) { + map.put(e.name().toLowerCase(Locale.ROOT), Set.of()); + } + + data.setEnumData(new CommandEnumData(component.name().toLowerCase(Locale.ROOT), map, false)); + } else { + data.setType(CommandParam.STRING); + } + + // This, realistically, is not going to be used without extensions using internals and implementing complicated commands. + // It essentially does the same behavior as JavaCommandsTranslator#isCompatible. + // But, selfishly, I would like to use it, and in the future it's possible extensions can register commands + // using Cloud, and in that case this becomes relevant! + if (bedrockData.isEmpty()) { + List list = new ArrayList<>(); + list.add(data); + bedrockData.add(list); + } else { + int size = bedrockData.size(); // Preserve original list size in case new entries get added. + for (int i = 0; i < size; i++) { + List cpdList = bedrockData.get(i); + if (cpdList.size() <= 1) { // No commands or parent will be root. + cpdList.add(data); + } else { + String parentName = node.parent().component().name(); // Should never be null. + if (!cpdList.get(cpdList.size() - 1).getName().equals(parentName)) { // We need to copy the list as this is a new branch. + for (int j = cpdList.size() - 2; j >= 0; j--) { + if (cpdList.get(j).getName().equals(parentName)) { + List newList = new ArrayList<>(cpdList.subList(0, j + 1)); + newList.add(data); + bedrockData.add(newList); + break; + } + } + } else { + cpdList.add(data); + } + } + } + } + + for (var child : node.children()) { + createParamData(child, bedrockData); + } + } } diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java index f189658cd..8eff3d16c 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/JavaCommandsTranslator.java @@ -41,6 +41,7 @@ import org.cloudburstmc.protocol.bedrock.data.command.*; import org.cloudburstmc.protocol.bedrock.packet.AvailableCommandsPacket; import org.geysermc.geyser.GeyserImpl; import org.geysermc.geyser.api.event.java.ServerDefineCommandsEvent; +import org.geysermc.geyser.api.util.PlatformType; import org.geysermc.geyser.command.CommandRegistry; import org.geysermc.geyser.item.enchantment.Enchantment; import org.geysermc.geyser.registry.BlockRegistries; @@ -213,6 +214,10 @@ public class JavaCommandsTranslator extends PacketTranslator