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.lib.TBMCCoreAPI;
|
||||
import buttondevteam.lib.chat.commands.CommandArgument;
|
||||
import buttondevteam.lib.chat.commands.NumberArg;
|
||||
import buttondevteam.lib.chat.commands.SubcommandData;
|
||||
import buttondevteam.lib.player.ChromaGamerBase;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
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.context.CommandContext;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
|
@ -16,6 +17,8 @@ import lombok.RequiredArgsConstructor;
|
|||
import lombok.val;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.javatuples.Pair;
|
||||
import org.javatuples.Triplet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
|
@ -235,33 +238,6 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
|||
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.
|
||||
* Always invoke {@link #registerCommandSuper(ICommand2)} when implementing this method.
|
||||
|
@ -277,14 +253,23 @@ 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)
|
||||
*/
|
||||
protected LiteralCommandNode<TP> registerCommandSuper(TC command) {
|
||||
LiteralCommandNode<TP> mainCommandNode = null;
|
||||
for (val meth : command.getClass().getMethods()) {
|
||||
val ann = meth.getAnnotation(Subcommand.class);
|
||||
if (ann == null) continue;
|
||||
String methodPath = getCommandPath(meth.getName(), ' ');
|
||||
registerNodeFromPath(command.getCommandPath() + methodPath)
|
||||
.addChild(getExecutableNode(meth, command, methodPath.substring(methodPath.lastIndexOf(' ') + 1)));
|
||||
val result = registerNodeFromPath(command.getCommandPath() + methodPath);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the node that can actually execute the given subcommand.
|
||||
|
@ -294,17 +279,19 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
|||
* @param path The command path
|
||||
* @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 paramMap = new HashMap<String, CommandArgument>();
|
||||
for (val param : params) {
|
||||
if (!Objects.equals(param.name, SENDER_ARG_NAME))
|
||||
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;
|
||||
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();
|
||||
}
|
||||
|
@ -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.
|
||||
*
|
||||
* @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(" ");
|
||||
CommandNode<TP> parent = dispatcher.getRoot();
|
||||
LiteralCommandNode<TP> mainCommand = null;
|
||||
for (int i = 0; i < split.length - 1; i++) {
|
||||
String part = split[i];
|
||||
var child = parent.getChild(part);
|
||||
if (child == null)
|
||||
parent.addChild(parent = CoreCommandBuilder.<TP, TC>literalNoOp(part).executes(this::executeHelpText).build());
|
||||
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
|
||||
*/
|
||||
private CommandArgument[] getCommandParameters(Method method) {
|
||||
val parameters = method.getParameterTypes();
|
||||
val parameters = method.getParameters();
|
||||
if (parameters.length == 0)
|
||||
throw new RuntimeException("No sender parameter for method '" + method + "'");
|
||||
val ret = new CommandArgument[parameters.length];
|
||||
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) {
|
||||
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 {
|
||||
val paramNames = usage.split(" ");
|
||||
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;
|
||||
}
|
||||
|
||||
private ArgumentType<?> getParameterType(Class<?> type) {
|
||||
// TODO: Move from registerTabcomplete
|
||||
private ArgumentType<?> getParameterType(CommandArgument arg) {
|
||||
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) {
|
||||
|
@ -450,42 +467,12 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
|||
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 String[] getCommandsText() {
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -5,7 +5,7 @@ import buttondevteam.lib.TBMCCoreAPI;
|
|||
import buttondevteam.lib.architecture.ButtonPlugin;
|
||||
import buttondevteam.lib.architecture.Component;
|
||||
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.RequiredArgumentBuilder;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
|
@ -333,38 +333,8 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
|
|||
Parameter[] parameters = subcmd.method.getParameters();
|
||||
for (int i = 1; i < parameters.length; i++) { //Skip sender
|
||||
Parameter parameter = parameters[i];
|
||||
ArgumentType<?> type;
|
||||
final Class<?> ptype = parameter.getType();
|
||||
final boolean customParamType;
|
||||
{
|
||||
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;
|
||||
}
|
||||
// TODO: Arg type
|
||||
val param = subcmd.parameters[i - 1];
|
||||
val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete.class))
|
||||
.map(CustomTabComplete::value);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package buttondevteam.lib.chat.commands;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.javatuples.Pair;
|
||||
|
||||
/**
|
||||
* A command argument's information to be used to construct the command.
|
||||
|
@ -9,5 +10,8 @@ import lombok.RequiredArgsConstructor;
|
|||
public class CommandArgument {
|
||||
public final String name;
|
||||
public final Class<?> type;
|
||||
public final boolean greedy;
|
||||
public final Pair<Double, Double> limits;
|
||||
public final boolean optional;
|
||||
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