diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/CommandAPIArgumentType.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/CommandAPIArgumentType.java index b665e82926..04e7692c8c 100644 --- a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/CommandAPIArgumentType.java +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/CommandAPIArgumentType.java @@ -102,6 +102,11 @@ public enum CommandAPIArgumentType { */ DIMENSION("minecraft:dimension"), + /** + * The DynamicMultiLiteralArgument + */ + DYNAMIC_MULTI_LITERAL, + /** * The EnchantmentArgument * diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgumentCommon.java b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgumentCommon.java new file mode 100644 index 0000000000..57e2ceaff1 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgumentCommon.java @@ -0,0 +1,55 @@ +package dev.jorel.commandapi.arguments; + +import com.mojang.brigadier.builder.ArgumentBuilder; +import dev.jorel.commandapi.commandnodes.DynamicMultiLiteralArgumentBuilder; + +import java.util.List; + +public interface DynamicMultiLiteralArgumentCommon +/// @endcond +, CommandSender> { + // DynamicMultiLiteralArgument info + @FunctionalInterface + interface LiteralsCreator { + List createLiterals(CommandSender sender); + } + + LiteralsCreator getLiteralsCreator(); + + /////////////////////////////////////////////////////////////////////////////////////////////////////// + // LINKED METHODS // + // These automatically link to methods in AbstractArgument (make sure they have the same signature) // + /////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Links to {@link AbstractArgument#getNodeName()}. + */ + String getNodeName(); + + /** + * Links to {@link AbstractArgument#isListed()}. + */ + boolean isListed(); + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // OVERRIDING METHODS // + // A DynamicMultiLiteralArgument has special logic that should override the implementations in AbstractArgument // + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** + * Overrides {@link AbstractArgument#createArgumentBuilder(List, List)}. + *

+ * We want to use DynamicMultiLiteralArgumentBuilder rather than RequiredArgumentBuilder. + */ + default ArgumentBuilder createArgumentBuilder(List previousArguments, List previousArgumentNames) { + String name = getNodeName(); + boolean isListed = isListed(); + LiteralsCreator literalsCreator = getLiteralsCreator(); + + previousArguments.add((Argument) this); + if (isListed()) previousArgumentNames.add(name); + + return DynamicMultiLiteralArgumentBuilder.dynamicMultiLiteral(name, isListed, literalsCreator); + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java new file mode 100644 index 0000000000..bb378ff3a3 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DifferentClientNode.java @@ -0,0 +1,148 @@ +package dev.jorel.commandapi.commandnodes; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.RedirectModifier; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.CommandAPIHandler; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; + +public abstract class DifferentClientNode extends ArgumentCommandNode { + // Rewrite node trees for the client + public static void rewriteAllChildren(Source client, CommandNode parent) { + // Copy the children, as we do expect them to be modified + List> children = new ArrayList<>(parent.getChildren()); + for (CommandNode child : children) { + rewriteChild(client, parent, child); + } + } + + public static void rewriteChild(Source client, CommandNode parent, CommandNode child) { + // Get the node creator + DifferentClientNode clientNodeCreator = null; + if (child instanceof DifferentClientNode) { + // child is directly a DifferentClientNode + clientNodeCreator = (DifferentClientNode) child; + } else if (child instanceof ArgumentCommandNode argument && argument.getType() instanceof Type) { + // child was copied from a DifferentClientNode using `createBuilder` (real node hidden in ArgumentType) + Type type = (Type) argument.getType(); + clientNodeCreator = type.node; + } + + if (clientNodeCreator != null) { + // Get the new client nodes + List> clientNodes = clientNodeCreator.rewriteNodeForClient(client); + + // Inject client node + Map> children = CommandAPIHandler.getCommandNodeChildren(parent); + children.remove(child.getName()); + for (CommandNode clientNode : clientNodes) { + children.put(clientNode.getName(), clientNode); + } + } + + // Modify all children + rewriteAllChildren(client, child); + } + + // Node information + private final ArgumentType type; + + public DifferentClientNode( + String name, ArgumentType type, + Command command, Predicate requirement, + CommandNode redirect, RedirectModifier modifier, boolean forks + ) { + // Platforms often copy this node as a normal ArgumentCommandNode, + // but we can hide a reference to ourselves in the ArgumentType used + // when ArgumentCommandNode#createBuilder is called + super(name, new Type(), command, requirement, redirect, modifier, forks, null); + + ((Type) super.getType()).node = this; + + // This type actually represents this argument when serializing nodes to json + // and also helps this argument blend in with ambiguity checks + this.type = type; + } + + // Handle type nonsense + private static class Type implements ArgumentType { + private DifferentClientNode node; + + @Override + public T parse(StringReader stringReader) { + throw new IllegalStateException("Not supposed to be called"); + } + } + + @Override + public ArgumentType getType() { + return type; + } + + @Override + public boolean isValidInput(String input) { + try { + StringReader reader = new StringReader(input); + this.type.parse(reader); + return !reader.canRead() || reader.peek() == ' '; + } catch (CommandSyntaxException var3) { + return false; + } + } + + @Override + public Collection getExamples() { + return this.type.getExamples(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof DifferentClientNode other)) return false; + + return this.type.equals(other.type) && super.equals(other); + } + + @Override + public int hashCode() { + int result = this.type.hashCode(); + result = 31 * result + super.hashCode(); + return result; + } + + // Special client-side nodes + + /** + * Transforms this node into one the client should see. + * + * @param client The client who the new node is for. If this is null, + * the generated node should just be for any general client. + * @return The version of this {@link CommandNode} the client should see. This is a list, so one server + * node may become many on the client. If the list is empty, the client will not see any nodes. + */ + public abstract List> rewriteNodeForClient(Source client); + + // Require inheritors to redefine these methods from ArgumentCommandNode + @Override + public abstract void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException; + + @Override + public abstract CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException; + + @Override + public abstract String toString(); +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java new file mode 100644 index 0000000000..6cb9aa24aa --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralArgumentBuilder.java @@ -0,0 +1,58 @@ +package dev.jorel.commandapi.commandnodes; + +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.tree.CommandNode; +import dev.jorel.commandapi.arguments.DynamicMultiLiteralArgumentCommon.LiteralsCreator; + +public class DynamicMultiLiteralArgumentBuilder extends ArgumentBuilder> { + // Build + private final String name; + private final boolean isListed; + private final LiteralsCreator literalsCreator; + + public DynamicMultiLiteralArgumentBuilder(String name, boolean isListed, LiteralsCreator literalsCreator) { + this.name = name; + this.isListed = isListed; + this.literalsCreator = literalsCreator; + } + + public static DynamicMultiLiteralArgumentBuilder dynamicMultiLiteral( + String name, boolean isListed, LiteralsCreator literalsCreator + ) { + return new DynamicMultiLiteralArgumentBuilder<>(name, isListed, literalsCreator); + } + + // Getters + @Override + protected DynamicMultiLiteralArgumentBuilder getThis() { + return this; + } + + public String getName() { + return name; + } + + public boolean isListed() { + return isListed; + } + + public LiteralsCreator getLiteralsCreator() { + return literalsCreator; + } + + // Create node + @Override + public DynamicMultiLiteralCommandNode build() { + final DynamicMultiLiteralCommandNode result = new DynamicMultiLiteralCommandNode<>( + name, isListed, literalsCreator, + getCommand(), getRequirement(), + getRedirect(), getRedirectModifier(), isFork() + ); + + for (final CommandNode argument : getArguments()) { + result.addChild(argument); + } + + return result; + } +} diff --git a/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java new file mode 100644 index 0000000000..8d8b177fd8 --- /dev/null +++ b/commandapi-core/src/main/java/dev/jorel/commandapi/commandnodes/DynamicMultiLiteralCommandNode.java @@ -0,0 +1,136 @@ +package dev.jorel.commandapi.commandnodes; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.RedirectModifier; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.context.ParsedArgument; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import dev.jorel.commandapi.CommandAPIHandler; +import dev.jorel.commandapi.arguments.DynamicMultiLiteralArgumentCommon.LiteralsCreator; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Predicate; + +public class DynamicMultiLiteralCommandNode extends DifferentClientNode { + private final boolean isListed; + private final LiteralsCreator literalsCreator; + + public DynamicMultiLiteralCommandNode( + String name, boolean isListed, LiteralsCreator literalsCreator, + Command command, Predicate requirement, + CommandNode redirect, RedirectModifier modifier, boolean forks + ) { + // This mostly acts like a StringArgument + super(name, StringArgumentType.word(), command, requirement, redirect, modifier, forks); + + this.isListed = isListed; + this.literalsCreator = literalsCreator; + } + + // Getters + public boolean isListed() { + return isListed; + } + + public LiteralsCreator getLiteralsCreator() { + return literalsCreator; + } + + // On the client, this node looks like a bunch of literal nodes + @Override + public List> rewriteNodeForClient(Source client) { + CommandAPIHandler commandAPIHandler = CommandAPIHandler.getInstance(); + CommandSender sender = commandAPIHandler.getPlatform().getCommandSenderFromCommandSource(client); + List literals = literalsCreator.createLiterals(sender); + + if (literals.isEmpty()) return List.of(); + + List> clientNodes = new ArrayList<>(literals.size()); + for (String literal : literals) { + LiteralCommandNode clientNode = new LiteralCommandNode<>( + literal, + getCommand(), getRequirement(), + getRedirect(), getRedirectModifier(), isFork() + ); + for (CommandNode child : getChildren()) { + clientNode.addChild(child); + } + clientNodes.add(clientNode); + } + + return clientNodes; + } + + // Implement CommandNode methods + @Override + public void parse(StringReader reader, CommandContextBuilder contextBuilder) throws CommandSyntaxException { + // Read the input + int start = reader.getCursor(); + String literal = reader.readUnquotedString(); + + // Validate input + CommandAPIHandler commandAPIHandler = CommandAPIHandler.getInstance(); + CommandSender client = commandAPIHandler.getPlatform().getCommandSenderFromCommandSource(contextBuilder.getSource()); + List literals = literalsCreator.createLiterals(client); + if (!literals.contains(literal)) { + reader.setCursor(start); + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.literalIncorrect().createWithContext(reader, literals); + } + + // Add node to list + ParsedArgument parsed = new ParsedArgument<>(start, reader.getCursor(), literal); + if (isListed) contextBuilder.withArgument(this.getName(), parsed); + contextBuilder.withNode(this, parsed.getRange()); + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException { + CommandAPIHandler commandAPIHandler = CommandAPIHandler.getInstance(); + CommandSender client = commandAPIHandler.getPlatform().getCommandSenderFromCommandSource(context.getSource()); + List literals = literalsCreator.createLiterals(client); + + String remaining = builder.getRemaining().toLowerCase(); + for (String literal : literals) { + if (literal.startsWith(remaining)) { + builder.suggest(literal); + } + } + return builder.buildFuture(); + } + + @Override + public boolean isValidInput(String input) { + // For ambiguity checking purposes, this node could accept any input + return true; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof DynamicMultiLiteralCommandNode other)) return false; + + if (this.isListed != other.isListed) return false; + if (!Objects.equals(this.literalsCreator, other.literalsCreator)) return false; + return super.equals(other); + } + + @Override + public int hashCode() { + int result = Objects.hash(this.isListed, this.literalsCreator); + result = 31 * result + super.hashCode(); + return result; + } + + @Override + public String toString() { + return ""; + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/pom.xml b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/pom.xml index cbeb49f3db..ebb83f797b 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/pom.xml +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/pom.xml @@ -57,6 +57,12 @@ ${paper.version} provided + + io.papermc.paper + paper-mojangapi + ${paper.version} + provided + dev.jorel diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java index 3312ad7865..8a587e983d 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/CommandAPIBukkit.java @@ -181,9 +181,9 @@ public void onEnable() { public void onServerLoad(ServerLoadEvent event) { CommandAPI.stopCommandRegistration(); } - }, getConfiguration().getPlugin()); + }, plugin); - paper.registerReloadHandler(plugin); + paper.registerEvents(plugin); } /* diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java index 018940d568..cf96526ecd 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/PaperImplementations.java @@ -1,8 +1,10 @@ package dev.jorel.commandapi; +import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.tree.RootCommandNode; +import dev.jorel.commandapi.commandnodes.DifferentClientNode; import dev.jorel.commandapi.exceptions.WrapperCommandSyntaxException; -import dev.jorel.commandapi.nms.NMS; import io.papermc.paper.event.server.ServerResourcesReloadedEvent; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; @@ -16,11 +18,11 @@ import org.bukkit.event.Listener; import org.bukkit.plugin.Plugin; -public class PaperImplementations { +public class PaperImplementations implements Listener { private final boolean isPaperPresent; private final boolean isFoliaPresent; - private final NMS nmsInstance; + private final CommandAPIBukkit nmsInstance; private final Class feedbackForwardingCommandSender; /** @@ -31,7 +33,7 @@ public class PaperImplementations { * @param nmsInstance The instance of NMS */ @SuppressWarnings("unchecked") - public PaperImplementations(boolean isPaperPresent, boolean isFoliaPresent, NMS nmsInstance) { + public PaperImplementations(boolean isPaperPresent, boolean isFoliaPresent, CommandAPIBukkit nmsInstance) { this.isPaperPresent = isPaperPresent; this.isFoliaPresent = isFoliaPresent; this.nmsInstance = nmsInstance; @@ -47,40 +49,57 @@ public PaperImplementations(boolean isPaperPresent, boolean isFoliaPresent, NMS< } /** - * Hooks into Paper's {@link ServerResourcesReloadedEvent} to detect if - * {@code /minecraft:reload} is called, and registers a reload handler that - * automatically calls the CommandAPI's internal datapack reloading function - * + * Registers paper-specific events (if paper is present), including: + *

    + *
  • {@link #onServerReloadResources(ServerResourcesReloadedEvent)}
  • + *
  • {@link #onCommandsSentToPlayer(AsyncPlayerSendCommandsEvent)}
  • + *
+ * * @param plugin the plugin that the CommandAPI is being used from */ - public void registerReloadHandler(Plugin plugin) { - if (isPaperPresent) { - Bukkit.getServer().getPluginManager().registerEvents(new Listener() { - @EventHandler - public void onServerReloadResources(ServerResourcesReloadedEvent event) { - // This event is called after Paper is done with everything command related - // which means we can put commands back - CommandAPIBukkit.get().getCommandRegistrationStrategy().preReloadDataPacks(); + public void registerEvents(Plugin plugin) { + if (!isPaperPresent) { + CommandAPI.logNormal("Did not hook into Paper events"); + return; + } + CommandAPI.logNormal("Hooking into Paper events"); + Bukkit.getServer().getPluginManager().registerEvents(this, plugin); + } - // Normally, the reloadDataPacks() method is responsible for updating commands for - // online players. If, however, datapacks aren't supposed to be reloaded upon /minecraft:reload - // we have to do this manually here. This won't have any effect on Spigot and Paper version prior to - // paper-1.20.6-65 - if (!CommandAPIBukkit.getConfiguration().shouldHookPaperReload()) { - for (Player player : Bukkit.getOnlinePlayers()) { - player.updateCommands(); - } - return; - } - CommandAPI.logNormal("/minecraft:reload detected. Reloading CommandAPI commands!"); - nmsInstance.reloadDataPacks(); - } + /** + * Automatically calls the CommandAPI's internal datapack + * reloading function when {@code /minecraft:reload} is called. + */ + @EventHandler + public void onServerReloadResources(ServerResourcesReloadedEvent event) { + // This event is called after Paper is done with everything command related + // which means we can put commands back + nmsInstance.getCommandRegistrationStrategy().preReloadDataPacks(); - }, plugin); - CommandAPI.logNormal("Hooked into Paper ServerResourcesReloadedEvent"); - } else { - CommandAPI.logNormal("Did not hook into Paper ServerResourcesReloadedEvent"); + // Normally, the reloadDataPacks() method is responsible for updating commands for + // online players. If, however, datapacks aren't supposed to be reloaded upon /minecraft:reload + // we have to do this manually here. This won't have any effect on Spigot and Paper version prior to + // paper-1.20.6-65 + if (!CommandAPIBukkit.getConfiguration().shouldHookPaperReload()) { + for (Player player : Bukkit.getOnlinePlayers()) { + player.updateCommands(); + } + return; } + CommandAPI.logNormal("/minecraft:reload detected. Reloading CommandAPI commands!"); + nmsInstance.reloadDataPacks(); + } + + @EventHandler + @SuppressWarnings("UnstableApiUsage") // This event is marked @Experimental, but it's been here since 1.15-ish + public void onCommandsSentToPlayer(AsyncPlayerSendCommandsEvent event) { + // This event fires twice, once async then sync + // We only want to do this once, and it is safe to run async + if (!event.isAsynchronous()) return; + + // Rewrite nodes to their client-side version when commands are sent to a client + Source source = nmsInstance.getBrigadierSourceFromCommandSender(event.getPlayer()); + DifferentClientNode.rewriteAllChildren(source, (RootCommandNode) event.getCommandNode()); } /** diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java index 8775599765..3c0c0c898d 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/SpigotCommandRegistration.java @@ -4,7 +4,9 @@ import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; +import dev.jorel.commandapi.commandnodes.DifferentClientNode; import dev.jorel.commandapi.preprocessor.RequireField; +import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.SimpleCommandMap; @@ -284,6 +286,17 @@ public void registerCommandNode(LiteralCommandNode node, String namespac CommandAPIHandler commandAPIHandler = CommandAPIHandler.getInstance(); RootCommandNode rootNode = brigadierDispatcher.getRoot(); + + if (!CommandAPIBukkit.get().getPaper().isPaperPresent()) { + // If Paper is not present, then we don't have the AsyncPlayerSendCommandsEvent, + // which allows us to rewrite nodes for a specific client. Best we can do on Spigot + // is rewrite the nodes now for a general client (represented by the console). This + // can still produce interesting server-client de-sync behavior that we want to use. + CommandSender console = Bukkit.getConsoleSender(); + Source source = CommandAPIBukkit.get().getBrigadierSourceFromCommandSender(console); + DifferentClientNode.rewriteAllChildren(source, node); + } + String name = node.getLiteral(); if (namespace.equals("minecraft")) { if (namespacesToFix.contains("minecraft:" + name)) { diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java new file mode 100644 index 0000000000..9d4d4e2298 --- /dev/null +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java @@ -0,0 +1,53 @@ +package dev.jorel.commandapi.arguments; + +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.jorel.commandapi.executors.CommandArguments; +import org.bukkit.command.CommandSender; + +import java.util.List; + +public class DynamicMultiLiteralArgument extends Argument implements DynamicMultiLiteralArgumentCommon, CommandSender> { + // Setup information + private final LiteralsCreator literalsCreator; + + public DynamicMultiLiteralArgument(String nodeName, LiteralsCreator literalsCreator) { + super(nodeName, null); + + this.literalsCreator = literalsCreator; + } + + @Override + public LiteralsCreator getLiteralsCreator() { + return literalsCreator; + } + + // Normal Argument stuff + @Override + public Class getPrimitiveType() { + return String.class; + } + + @Override + public CommandAPIArgumentType getArgumentType() { + return CommandAPIArgumentType.DYNAMIC_MULTI_LITERAL; + } + + @Override + public String parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { + return cmdCtx.getArgument(key, String.class); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // DynamicMultiLiteralArgumentCommon interface overrides // + // When a method in a parent class and interface have the same signature, Java will call the class version of the // + // method by default. However, we want to use the implementations found in the DynamicMultiLiteralArgumentCommon // + // interface. // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { + return DynamicMultiLiteralArgumentCommon.super.createArgumentBuilder(previousArguments, previousArgumentNames); + } +} diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java index 73fb549895..a03a32759e 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java @@ -21,13 +21,16 @@ package dev.jorel.commandapi; import java.io.File; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import dev.jorel.commandapi.arguments.*; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; import org.bukkit.plugin.InvalidPluginException; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; @@ -131,5 +134,49 @@ private JavaPlugin getAndValidatePlugin(String pluginName) { @Override public void onEnable() { CommandAPI.onEnable(); + + List tags = new ArrayList<>(); + tags.add("hello"); + tags.add("world"); + + new CommandTree("server") + .then( + new LiteralArgument("add").then( + new StringArgument("tag").executes(info -> { + String tag = info.args().getUnchecked("tag"); + + tags.add(tag); + }) + ) + ) + .then( + new LiteralArgument("tag").then( + new DynamicMultiLiteralArgument("tag", sender -> { + if (sender instanceof Player player) { + List addPlayer = new ArrayList<>(tags); + addPlayer.add(player.getName()); + return addPlayer; + } else { + return tags; + } + }).then(new IntegerArgument("extra").replaceSafeSuggestions(SafeSuggestions.suggest(1, 2, 3)) + .executes(info -> { + String tag = info.args().getUnchecked("tag"); + int extra = info.args().getUnchecked("extra"); + + info.sender().sendMessage(tag + " " + extra); + } + ) + )) + ) + .register(); + + new CommandAPICommand("updateCommands") + .executes(info -> { + for (Player player : Bukkit.getOnlinePlayers()) { + CommandAPI.updateRequirements(player); + } + }) + .register(); } } diff --git a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/pom.xml b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/pom.xml index 1cd51fb33c..8d7895baf0 100644 --- a/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/pom.xml +++ b/commandapi-platforms/commandapi-bukkit/commandapi-bukkit-test/commandapi-bukkit-test-tests/pom.xml @@ -103,6 +103,14 @@ provided + + + io.papermc.paper + paper-mojangapi + 1.20.6-R0.1-SNAPSHOT + provided + + com.github.zafarkhaja diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java index af6cac6e1d..aadd971b97 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/CommandAPIVelocity.java @@ -14,11 +14,14 @@ import com.mojang.brigadier.tree.RootCommandNode; import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent; import com.velocitypowered.api.proxy.ConsoleCommandSource; import dev.jorel.commandapi.arguments.Argument; import dev.jorel.commandapi.arguments.LiteralArgument; import dev.jorel.commandapi.arguments.MultiLiteralArgument; import dev.jorel.commandapi.arguments.SuggestionProviders; +import dev.jorel.commandapi.commandnodes.DifferentClientNode; import org.apache.logging.log4j.LogManager; import java.io.File; @@ -88,7 +91,15 @@ private static void setInternalConfig(InternalVelocityConfig internalVelocityCon @Override public void onEnable() { - // Nothing to do + // Register events + config.getServer().getEventManager().register(config.getPlugin(), this); + } + + @Subscribe + @SuppressWarnings("UnstableApiUsage") // This event is marked @Beta + public void onCommandsSentToPlayer(PlayerAvailableCommandsEvent event) { + // Rewrite nodes to their client-side version when commands are sent to a client + DifferentClientNode.rewriteAllChildren(event.getPlayer(), (RootCommandNode) event.getRootNode()); } @Override diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java new file mode 100644 index 0000000000..dc9c704570 --- /dev/null +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-core/src/main/java/dev/jorel/commandapi/arguments/DynamicMultiLiteralArgument.java @@ -0,0 +1,53 @@ +package dev.jorel.commandapi.arguments; + +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.velocitypowered.api.command.CommandSource; +import dev.jorel.commandapi.executors.CommandArguments; + +import java.util.List; + +public class DynamicMultiLiteralArgument extends Argument implements DynamicMultiLiteralArgumentCommon, CommandSource> { + // Setup information + private final LiteralsCreator literalsCreator; + + public DynamicMultiLiteralArgument(String nodeName, LiteralsCreator literalsCreator) { + super(nodeName, null); + + this.literalsCreator = literalsCreator; + } + + @Override + public LiteralsCreator getLiteralsCreator() { + return literalsCreator; + } + + // Normal Argument stuff + @Override + public Class getPrimitiveType() { + return String.class; + } + + @Override + public CommandAPIArgumentType getArgumentType() { + return CommandAPIArgumentType.DYNAMIC_MULTI_LITERAL; + } + + @Override + public String parseArgument(CommandContext cmdCtx, String key, CommandArguments previousArgs) throws CommandSyntaxException { + return cmdCtx.getArgument(key, String.class); + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // DynamicMultiLiteralArgumentCommon interface overrides // + // When a method in a parent class and interface have the same signature, Java will call the class version of the // + // method by default. However, we want to use the implementations found in the DynamicMultiLiteralArgumentCommon // + // interface. // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + @Override + public ArgumentBuilder createArgumentBuilder(List> previousArguments, List previousArgumentNames) { + return DynamicMultiLiteralArgumentCommon.super.createArgumentBuilder(previousArguments, previousArgumentNames); + } +} diff --git a/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java b/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java index b6458cdb61..23da2fe04a 100644 --- a/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java +++ b/commandapi-platforms/commandapi-velocity/commandapi-velocity-plugin/src/main/java/dev/jorel/commandapi/CommandAPIMain.java @@ -7,7 +7,10 @@ import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.ProxyServer; +import dev.jorel.commandapi.arguments.*; +import net.kyori.adventure.text.Component; import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.yaml.YAMLConfigurationLoader; @@ -16,6 +19,8 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Logger; /** @@ -73,6 +78,42 @@ public CommandAPIMain(ProxyServer server, Logger logger, @DataDirectory Path dat public void onProxyInitialization(ProxyInitializeEvent event) { // Enable CommandAPI.onEnable(); + + List tags = new ArrayList<>(); + tags.add("hello"); + tags.add("world"); + + new CommandTree("proxy") + .then( + new LiteralArgument("add").then( + new StringArgument("tag").executes(info -> { + String tag = info.args().getUnchecked("tag"); + + tags.add(tag); + }) + ) + ) + .then( + new LiteralArgument("tag").then( + new DynamicMultiLiteralArgument("tag", sender -> { + if (sender instanceof Player player) { + List addPlayer = new ArrayList<>(tags); + addPlayer.add(player.getUsername()); + return addPlayer; + } else { + return tags; + } + }).then(new IntegerArgument("extra").replaceSafeSuggestions(SafeSuggestions.suggest(1, 2, 3)) + .executes(info -> { + String tag = info.args().getUnchecked("tag"); + int extra = info.args().getUnchecked("extra"); + + info.sender().sendMessage(Component.text(tag + " " + extra)); + }) + ) + ) + ) + .register(); } @Subscribe