Add argument type handling and add return type for registerCommandSuper
- Also added help text back and cleaned some stuff up - Added support for number argument limits
This commit is contained in:
parent
05477641a4
commit
b53813fa2e
4 changed files with 85 additions and 104 deletions
|
@ -3,11 +3,12 @@ package buttondevteam.lib.chat;
|
||||||
import buttondevteam.core.MainPlugin;
|
import buttondevteam.core.MainPlugin;
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
import buttondevteam.lib.chat.commands.CommandArgument;
|
import buttondevteam.lib.chat.commands.CommandArgument;
|
||||||
|
import buttondevteam.lib.chat.commands.NumberArg;
|
||||||
import buttondevteam.lib.chat.commands.SubcommandData;
|
import buttondevteam.lib.chat.commands.SubcommandData;
|
||||||
import buttondevteam.lib.player.ChromaGamerBase;
|
import buttondevteam.lib.player.ChromaGamerBase;
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
import com.mojang.brigadier.ParseResults;
|
import com.mojang.brigadier.ParseResults;
|
||||||
import com.mojang.brigadier.arguments.ArgumentType;
|
import com.mojang.brigadier.arguments.*;
|
||||||
import com.mojang.brigadier.builder.ArgumentBuilder;
|
import com.mojang.brigadier.builder.ArgumentBuilder;
|
||||||
import com.mojang.brigadier.context.CommandContext;
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
import com.mojang.brigadier.tree.CommandNode;
|
import com.mojang.brigadier.tree.CommandNode;
|
||||||
|
@ -16,6 +17,8 @@ import lombok.RequiredArgsConstructor;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.javatuples.Pair;
|
||||||
|
import org.javatuples.Triplet;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
@ -235,33 +238,6 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a command node for the given subcommand that can be used for a custom registering logic (Discord).
|
|
||||||
*
|
|
||||||
* @param command The command object
|
|
||||||
* @param method The subcommand method
|
|
||||||
* @return The processed command node
|
|
||||||
* @throws Exception Something broke
|
|
||||||
*/
|
|
||||||
protected CoreCommandNode<TP, TC> getSubcommandNode(TC command, Method method, Subcommand subcommand) throws Exception {
|
|
||||||
var pdata = getParameterData(method);
|
|
||||||
val arguments = new HashMap<String, CommandArgument>(pdata.length - 1);
|
|
||||||
for (var param : pdata) {
|
|
||||||
arguments.put(param.name, param);
|
|
||||||
}
|
|
||||||
// TODO: Dynamic help text
|
|
||||||
//return new SubcommandData<>(pdata[0].type, command.getCommandPath() + getCommandPath(method.getName(), ' '), arguments, command, command.getHelpText(method, subcommand), null);
|
|
||||||
return getSubcommandNode(method, pdata[0].type, command).helps(command.getHelpText(method, subcommand)).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get parameter data for the given subcommand. Attempts to read it from the commands file, if it fails, it will return generic info.
|
|
||||||
*
|
|
||||||
* @param method The method the subcommand is created from
|
|
||||||
* @param i The index to use if no name was found
|
|
||||||
* @return Parameter data object
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a command in the command system. The way this command gets registered may change depending on the implementation.
|
* Register a command in the command system. The way this command gets registered may change depending on the implementation.
|
||||||
* Always invoke {@link #registerCommandSuper(ICommand2)} when implementing this method.
|
* Always invoke {@link #registerCommandSuper(ICommand2)} when implementing this method.
|
||||||
|
@ -277,13 +253,22 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
* @return The Brigadier command node if you need it for something (like tab completion)
|
* @return The Brigadier command node if you need it for something (like tab completion)
|
||||||
*/
|
*/
|
||||||
protected LiteralCommandNode<TP> registerCommandSuper(TC command) {
|
protected LiteralCommandNode<TP> registerCommandSuper(TC command) {
|
||||||
|
LiteralCommandNode<TP> mainCommandNode = null;
|
||||||
for (val meth : command.getClass().getMethods()) {
|
for (val meth : command.getClass().getMethods()) {
|
||||||
val ann = meth.getAnnotation(Subcommand.class);
|
val ann = meth.getAnnotation(Subcommand.class);
|
||||||
if (ann == null) continue;
|
if (ann == null) continue;
|
||||||
String methodPath = getCommandPath(meth.getName(), ' ');
|
String methodPath = getCommandPath(meth.getName(), ' ');
|
||||||
registerNodeFromPath(command.getCommandPath() + methodPath)
|
val result = registerNodeFromPath(command.getCommandPath() + methodPath);
|
||||||
.addChild(getExecutableNode(meth, command, methodPath.substring(methodPath.lastIndexOf(' ') + 1)));
|
result.getValue0().addChild(getExecutableNode(meth, command, ann, result.getValue2()));
|
||||||
|
if (mainCommandNode == null) mainCommandNode = result.getValue1();
|
||||||
|
else if (!result.getValue1().getName().equals(mainCommandNode.getName())) {
|
||||||
|
MainPlugin.Instance.getLogger().warning("Multiple commands are defined in the same class! This is not supported. Class: " + command.getClass().getSimpleName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (mainCommandNode == null) {
|
||||||
|
throw new RuntimeException("There are no subcommands defined in the command class " + command.getClass().getSimpleName() + "!");
|
||||||
|
}
|
||||||
|
return mainCommandNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -294,17 +279,19 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
* @param path The command path
|
* @param path The command path
|
||||||
* @return The executable node
|
* @return The executable node
|
||||||
*/
|
*/
|
||||||
private LiteralCommandNode<TP> getExecutableNode(Method method, TC command, String path) {
|
private LiteralCommandNode<TP> getExecutableNode(Method method, TC command, Subcommand ann, String path) {
|
||||||
val params = getCommandParameters(method); // Param order is important
|
val params = getCommandParameters(method); // Param order is important
|
||||||
val paramMap = new HashMap<String, CommandArgument>();
|
val paramMap = new HashMap<String, CommandArgument>();
|
||||||
for (val param : params) {
|
for (val param : params) {
|
||||||
if (!Objects.equals(param.name, SENDER_ARG_NAME))
|
if (!Objects.equals(param.name, SENDER_ARG_NAME))
|
||||||
paramMap.put(param.name, param);
|
paramMap.put(param.name, param);
|
||||||
}
|
}
|
||||||
val node = CoreCommandBuilder.<TP, TC>literal(path, params[0].type, paramMap, command).executes(this::executeCommand);
|
val node = CoreCommandBuilder.<TP, TC>literal(path, params[0].type, paramMap, command)
|
||||||
|
.helps(command.getHelpText(method, ann)).executes(this::executeCommand);
|
||||||
ArgumentBuilder<TP, ?> parent = node;
|
ArgumentBuilder<TP, ?> parent = node;
|
||||||
for (val param : params) { // Register parameters in the right order
|
for (val param : params) { // Register parameters in the right order
|
||||||
parent.then(parent = CoreArgumentBuilder.argument(param.name, getParameterType(param.type), false)); // TODO: Optional arg
|
if (!Objects.equals(param.name, SENDER_ARG_NAME))
|
||||||
|
parent.then(parent = CoreArgumentBuilder.argument(param.name, getParameterType(param), param.optional));
|
||||||
}
|
}
|
||||||
return node.build();
|
return node.build();
|
||||||
}
|
}
|
||||||
|
@ -313,19 +300,22 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
* Registers all necessary no-op nodes for the given path.
|
* Registers all necessary no-op nodes for the given path.
|
||||||
*
|
*
|
||||||
* @param path The full command path
|
* @param path The full command path
|
||||||
* @return The last no-op node that can be used to register the executable node
|
* @return The last no-op node that can be used to register the executable node,
|
||||||
|
* the main command node and the last part of the command path (that isn't registered yet)
|
||||||
*/
|
*/
|
||||||
private CommandNode<TP> registerNodeFromPath(String path) {
|
private Triplet<CommandNode<TP>, LiteralCommandNode<TP>, String> registerNodeFromPath(String path) {
|
||||||
String[] split = path.split(" ");
|
String[] split = path.split(" ");
|
||||||
CommandNode<TP> parent = dispatcher.getRoot();
|
CommandNode<TP> parent = dispatcher.getRoot();
|
||||||
|
LiteralCommandNode<TP> mainCommand = null;
|
||||||
for (int i = 0; i < split.length - 1; i++) {
|
for (int i = 0; i < split.length - 1; i++) {
|
||||||
String part = split[i];
|
String part = split[i];
|
||||||
var child = parent.getChild(part);
|
var child = parent.getChild(part);
|
||||||
if (child == null)
|
if (child == null)
|
||||||
parent.addChild(parent = CoreCommandBuilder.<TP, TC>literalNoOp(part).executes(this::executeHelpText).build());
|
parent.addChild(parent = CoreCommandBuilder.<TP, TC>literalNoOp(part).executes(this::executeHelpText).build());
|
||||||
else parent = child;
|
else parent = child;
|
||||||
|
if (i == 0) mainCommand = (LiteralCommandNode<TP>) parent; // Has to be a literal, if not, well, error
|
||||||
}
|
}
|
||||||
return parent;
|
return new Triplet<>(parent, mainCommand, split[split.length - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -337,27 +327,54 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
* @throws RuntimeException If there is no sender parameter declared in the method
|
* @throws RuntimeException If there is no sender parameter declared in the method
|
||||||
*/
|
*/
|
||||||
private CommandArgument[] getCommandParameters(Method method) {
|
private CommandArgument[] getCommandParameters(Method method) {
|
||||||
val parameters = method.getParameterTypes();
|
val parameters = method.getParameters();
|
||||||
if (parameters.length == 0)
|
if (parameters.length == 0)
|
||||||
throw new RuntimeException("No sender parameter for method '" + method + "'");
|
throw new RuntimeException("No sender parameter for method '" + method + "'");
|
||||||
val ret = new CommandArgument[parameters.length];
|
val ret = new CommandArgument[parameters.length];
|
||||||
val usage = getParameterHelp(method);
|
val usage = getParameterHelp(method);
|
||||||
ret[0] = new CommandArgument(SENDER_ARG_NAME, parameters[0], "Sender");
|
ret[0] = new CommandArgument(SENDER_ARG_NAME, parameters[0].getType(), false, null, false, "Sender");
|
||||||
if (usage == null) {
|
if (usage == null) {
|
||||||
for (int i = 1; i < parameters.length; i++) {
|
for (int i = 1; i < parameters.length; i++) {
|
||||||
ret[i] = new CommandArgument("param" + i, parameters[i], "param" + i);
|
ret[i] = new CommandArgument("param" + i, parameters[i].getType(), false, null, false, "param" + i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val paramNames = usage.split(" ");
|
val paramNames = usage.split(" ");
|
||||||
for (int i = 1; i < parameters.length; i++) {
|
for (int i = 1; i < parameters.length; i++) {
|
||||||
ret[i] = new CommandArgument(paramNames[i], parameters[i], paramNames[i]); // TODO: Description (JavaDoc?)
|
val numAnn = parameters[i].getAnnotation(NumberArg.class);
|
||||||
|
ret[i] = new CommandArgument(paramNames[i], parameters[i].getType(),
|
||||||
|
parameters[i].isVarArgs() || parameters[i].isAnnotationPresent(TextArg.class),
|
||||||
|
numAnn == null ? null : new Pair<>(numAnn.lowerLimit(), numAnn.upperLimit()),
|
||||||
|
parameters[i].isAnnotationPresent(OptionalArg.class),
|
||||||
|
paramNames[i]); // TODO: Description (JavaDoc?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArgumentType<?> getParameterType(Class<?> type) {
|
private ArgumentType<?> getParameterType(CommandArgument arg) {
|
||||||
// TODO: Move from registerTabcomplete
|
final Class<?> ptype = arg.type;
|
||||||
|
Number lowerLimit = Double.NEGATIVE_INFINITY, upperLimit = Double.POSITIVE_INFINITY;
|
||||||
|
if (arg.greedy)
|
||||||
|
return StringArgumentType.greedyString();
|
||||||
|
else if (ptype == String.class)
|
||||||
|
return StringArgumentType.word();
|
||||||
|
else if (ptype == int.class || ptype == Integer.class
|
||||||
|
|| ptype == byte.class || ptype == Byte.class
|
||||||
|
|| ptype == short.class || ptype == Short.class)
|
||||||
|
return IntegerArgumentType.integer(lowerLimit.intValue(), upperLimit.intValue());
|
||||||
|
else if (ptype == long.class || ptype == Long.class)
|
||||||
|
return LongArgumentType.longArg(lowerLimit.longValue(), upperLimit.longValue());
|
||||||
|
else if (ptype == float.class || ptype == Float.class)
|
||||||
|
return FloatArgumentType.floatArg(lowerLimit.floatValue(), upperLimit.floatValue());
|
||||||
|
else if (ptype == double.class || ptype == Double.class)
|
||||||
|
return DoubleArgumentType.doubleArg(lowerLimit.doubleValue(), upperLimit.doubleValue());
|
||||||
|
else if (ptype == char.class || ptype == Character.class)
|
||||||
|
return StringArgumentType.word();
|
||||||
|
else if (ptype == boolean.class || ptype == Boolean.class)
|
||||||
|
return BoolArgumentType.bool();
|
||||||
|
else {
|
||||||
|
return StringArgumentType.word();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int executeHelpText(CommandContext<TP> context) {
|
private int executeHelpText(CommandContext<TP> context) {
|
||||||
|
@ -450,42 +467,12 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerCommand(String path, String methodName, Subcommand ann, SubcommandData<TC> sd) {
|
|
||||||
val subcommand = commandChar + path + getCommandPath(methodName, ' ');
|
|
||||||
subcommands.put(subcommand, sd);
|
|
||||||
for (String alias : ann.aliases())
|
|
||||||
subcommands.put(commandChar + path + alias, sd);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract boolean hasPermission(TP sender, TC command, Method subcommand);
|
public abstract boolean hasPermission(TP sender, TC command, Method subcommand);
|
||||||
|
|
||||||
public String[] getCommandsText() {
|
public String[] getCommandsText() {
|
||||||
return commandHelp.toArray(new String[0]);
|
return commandHelp.toArray(new String[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters all of the subcommands in the given command.
|
|
||||||
*
|
|
||||||
* @param command The command object
|
|
||||||
*/
|
|
||||||
public void unregisterCommand(ICommand2<TP> command) {
|
|
||||||
var path = command.getCommandPath();
|
|
||||||
for (val method : command.getClass().getMethods()) {
|
|
||||||
val ann = method.getAnnotation(Subcommand.class);
|
|
||||||
if (ann == null) continue;
|
|
||||||
unregisterCommand(path, method.getName(), ann);
|
|
||||||
for (String p : command.getCommandPaths())
|
|
||||||
unregisterCommand(p, method.getName(), ann);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unregisterCommand(String path, String methodName, Subcommand ann) {
|
|
||||||
val subcommand = commandChar + path + getCommandPath(methodName, ' ');
|
|
||||||
subcommands.remove(subcommand);
|
|
||||||
for (String alias : ann.aliases())
|
|
||||||
subcommands.remove(commandChar + path + alias);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* It will start with the given replace char.
|
* It will start with the given replace char.
|
||||||
*
|
*
|
||||||
|
|
|
@ -5,7 +5,7 @@ import buttondevteam.lib.TBMCCoreAPI;
|
||||||
import buttondevteam.lib.architecture.ButtonPlugin;
|
import buttondevteam.lib.architecture.ButtonPlugin;
|
||||||
import buttondevteam.lib.architecture.Component;
|
import buttondevteam.lib.architecture.Component;
|
||||||
import buttondevteam.lib.player.ChromaGamerBase;
|
import buttondevteam.lib.player.ChromaGamerBase;
|
||||||
import com.mojang.brigadier.arguments.*;
|
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||||
import com.mojang.brigadier.tree.CommandNode;
|
import com.mojang.brigadier.tree.CommandNode;
|
||||||
|
@ -333,38 +333,8 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
|
||||||
Parameter[] parameters = subcmd.method.getParameters();
|
Parameter[] parameters = subcmd.method.getParameters();
|
||||||
for (int i = 1; i < parameters.length; i++) { //Skip sender
|
for (int i = 1; i < parameters.length; i++) { //Skip sender
|
||||||
Parameter parameter = parameters[i];
|
Parameter parameter = parameters[i];
|
||||||
ArgumentType<?> type;
|
|
||||||
final Class<?> ptype = parameter.getType();
|
|
||||||
final boolean customParamType;
|
final boolean customParamType;
|
||||||
{
|
// TODO: Arg type
|
||||||
boolean customParamTypeTemp = false;
|
|
||||||
if (ptype == String.class)
|
|
||||||
if (parameter.isAnnotationPresent(TextArg.class))
|
|
||||||
type = StringArgumentType.greedyString();
|
|
||||||
else
|
|
||||||
type = StringArgumentType.word();
|
|
||||||
else if (ptype == int.class || ptype == Integer.class
|
|
||||||
|| ptype == byte.class || ptype == Byte.class
|
|
||||||
|| ptype == short.class || ptype == Short.class)
|
|
||||||
type = IntegerArgumentType.integer(); //TODO: Min, max
|
|
||||||
else if (ptype == long.class || ptype == Long.class)
|
|
||||||
type = LongArgumentType.longArg();
|
|
||||||
else if (ptype == float.class || ptype == Float.class)
|
|
||||||
type = FloatArgumentType.floatArg();
|
|
||||||
else if (ptype == double.class || ptype == Double.class)
|
|
||||||
type = DoubleArgumentType.doubleArg();
|
|
||||||
else if (ptype == char.class || ptype == Character.class)
|
|
||||||
type = StringArgumentType.word();
|
|
||||||
else if (ptype == boolean.class || ptype == Boolean.class)
|
|
||||||
type = BoolArgumentType.bool();
|
|
||||||
else if (parameter.isVarArgs())
|
|
||||||
type = StringArgumentType.greedyString();
|
|
||||||
else {
|
|
||||||
type = StringArgumentType.word();
|
|
||||||
customParamTypeTemp = true;
|
|
||||||
}
|
|
||||||
customParamType = customParamTypeTemp;
|
|
||||||
}
|
|
||||||
val param = subcmd.parameters[i - 1];
|
val param = subcmd.parameters[i - 1];
|
||||||
val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete.class))
|
val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete.class))
|
||||||
.map(CustomTabComplete::value);
|
.map(CustomTabComplete::value);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package buttondevteam.lib.chat.commands;
|
package buttondevteam.lib.chat.commands;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.javatuples.Pair;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A command argument's information to be used to construct the command.
|
* A command argument's information to be used to construct the command.
|
||||||
|
@ -9,5 +10,8 @@ import lombok.RequiredArgsConstructor;
|
||||||
public class CommandArgument {
|
public class CommandArgument {
|
||||||
public final String name;
|
public final String name;
|
||||||
public final Class<?> type;
|
public final Class<?> type;
|
||||||
|
public final boolean greedy;
|
||||||
|
public final Pair<Double, Double> limits;
|
||||||
|
public final boolean optional;
|
||||||
public final String description;
|
public final String description;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package buttondevteam.lib.chat.commands;
|
||||||
|
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A command argument that can have a number as a value.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface NumberArg {
|
||||||
|
/**
|
||||||
|
* The highest value that can be used for this argument.
|
||||||
|
*/
|
||||||
|
double upperLimit() default Double.POSITIVE_INFINITY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The lowest value that can be used for this argument.
|
||||||
|
*/
|
||||||
|
double lowerLimit() default Double.NEGATIVE_INFINITY;
|
||||||
|
}
|
Loading…
Reference in a new issue