Added documentation and refactored commands.yml handling
- Added command argument help manager to read the arguments - MC tab completion still needs to be fixed
This commit is contained in:
parent
47178e7f7c
commit
db08d9baee
4 changed files with 103 additions and 70 deletions
|
@ -3,6 +3,7 @@ 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.CommandArgumentHelpManager;
|
||||||
import buttondevteam.lib.chat.commands.NumberArg;
|
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;
|
||||||
|
@ -16,12 +17,10 @@ import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
import lombok.RequiredArgsConstructor;
|
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.javatuples.Pair;
|
import org.javatuples.Pair;
|
||||||
import org.javatuples.Triplet;
|
import org.javatuples.Triplet;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
@ -80,31 +79,6 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
public @interface OptionalArg {
|
public @interface OptionalArg {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*protected static class SubcommandHelpData<T extends ICommand2> extends SubcommandData<T> {
|
|
||||||
private final TreeSet<String> ht = new TreeSet<>();
|
|
||||||
private BukkitTask task;
|
|
||||||
|
|
||||||
public SubcommandHelpData(Method method, T command, String[] helpText) {
|
|
||||||
super(method, command, helpText);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addSubcommand(String command) {
|
|
||||||
ht.add(command);
|
|
||||||
if (task == null)
|
|
||||||
task = Bukkit.getScheduler().runTask(MainPlugin.Instance, () -> {
|
|
||||||
helpText = new String[ht.size() + 1]; //This will only run after the server is started List<E> list = new ArrayList<E>(size());
|
|
||||||
helpText[0] = "§6---- Subcommands ----"; //TODO: There may be more to the help text
|
|
||||||
int i = 1;
|
|
||||||
for (Iterator<String> iterator = ht.iterator();
|
|
||||||
iterator.hasNext() && i < helpText.length; i++) {
|
|
||||||
String e = iterator.next();
|
|
||||||
helpText[i] = e;
|
|
||||||
}
|
|
||||||
task = null; //Run again, if needed
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
protected static class ParamConverter<T> {
|
protected static class ParamConverter<T> {
|
||||||
public final Function<String, T> converter;
|
public final Function<String, T> converter;
|
||||||
|
@ -205,7 +179,7 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
if (ann == null) continue;
|
if (ann == null) continue;
|
||||||
String methodPath = getCommandPath(meth.getName(), ' ');
|
String methodPath = getCommandPath(meth.getName(), ' ');
|
||||||
val result = registerNodeFromPath(command.getCommandPath() + methodPath);
|
val result = registerNodeFromPath(command.getCommandPath() + methodPath);
|
||||||
result.getValue0().addChild(getExecutableNode(meth, command, ann, result.getValue2()));
|
result.getValue0().addChild(getExecutableNode(meth, command, ann, result.getValue2(), new CommandArgumentHelpManager<>(command)));
|
||||||
if (mainCommandNode == null) mainCommandNode = result.getValue1();
|
if (mainCommandNode == null) mainCommandNode = result.getValue1();
|
||||||
else if (!result.getValue1().getName().equals(mainCommandNode.getName())) {
|
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());
|
MainPlugin.Instance.getLogger().warning("Multiple commands are defined in the same class! This is not supported. Class: " + command.getClass().getSimpleName());
|
||||||
|
@ -225,8 +199,8 @@ 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, Subcommand ann, String path) {
|
private LiteralCommandNode<TP> getExecutableNode(Method method, TC command, Subcommand ann, String path, CommandArgumentHelpManager<TC, TP> argHelpManager) {
|
||||||
val paramsAndSenderType = getCommandParameters(method); // Param order is important
|
val paramsAndSenderType = getCommandParametersAndSender(method, argHelpManager); // Param order is important
|
||||||
val params = paramsAndSenderType.getValue0();
|
val params = paramsAndSenderType.getValue0();
|
||||||
val paramMap = new HashMap<String, CommandArgument>();
|
val paramMap = new HashMap<String, CommandArgument>();
|
||||||
for (val param : params) {
|
for (val param : params) {
|
||||||
|
@ -237,7 +211,7 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
.executes(this::executeCommand);
|
.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), param.optional));
|
parent.then(parent = CoreArgumentBuilder.argument(param.name, getArgumentType(param), param.optional));
|
||||||
}
|
}
|
||||||
return node.build();
|
return node.build();
|
||||||
}
|
}
|
||||||
|
@ -272,12 +246,12 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
* @return Parameter data objects and the sender type
|
* @return Parameter data objects and the sender type
|
||||||
* @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 Pair<CommandArgument[], Class<?>> getCommandParameters(Method method) {
|
private Pair<CommandArgument[], Class<?>> getCommandParametersAndSender(Method method, CommandArgumentHelpManager<TC, TP> argHelpManager) {
|
||||||
val parameters = method.getParameters();
|
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 = argHelpManager.getParameterHelpForMethod(method);
|
||||||
val paramNames = usage != null ? usage.split(" ") : null;
|
val paramNames = usage != null ? usage.split(" ") : null;
|
||||||
for (int i = 1; i < parameters.length; i++) {
|
for (int i = 1; i < parameters.length; i++) {
|
||||||
val numAnn = parameters[i].getAnnotation(NumberArg.class);
|
val numAnn = parameters[i].getAnnotation(NumberArg.class);
|
||||||
|
@ -290,7 +264,14 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
return new Pair<>(ret, parameters[0].getType());
|
return new Pair<>(ret, parameters[0].getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArgumentType<?> getParameterType(CommandArgument arg) {
|
/**
|
||||||
|
* Converts the Chroma representation of the argument declaration into Brigadier format.
|
||||||
|
* It does part of the command argument type processing.
|
||||||
|
*
|
||||||
|
* @param arg Our representation of the command argument
|
||||||
|
* @return The Brigadier representation of the command argument
|
||||||
|
*/
|
||||||
|
private ArgumentType<?> getArgumentType(CommandArgument arg) {
|
||||||
final Class<?> ptype = arg.type;
|
final Class<?> ptype = arg.type;
|
||||||
Number lowerLimit = arg.limits.getValue0(), upperLimit = arg.limits.getValue1();
|
Number lowerLimit = arg.limits.getValue0(), upperLimit = arg.limits.getValue1();
|
||||||
if (arg.greedy)
|
if (arg.greedy)
|
||||||
|
@ -316,11 +297,24 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the help text based on the executed command. Each command node might have a help text stored.
|
||||||
|
* The help text is displayed either because of incorrect usage or it's explicitly requested.
|
||||||
|
*
|
||||||
|
* @param context The command context
|
||||||
|
* @return Vanilla command success level (0)
|
||||||
|
*/
|
||||||
private int executeHelpText(CommandContext<TP> context) {
|
private int executeHelpText(CommandContext<TP> context) {
|
||||||
System.out.println("Nodes:\n" + context.getNodes().stream().map(node -> node.getNode().getName() + "@" + node.getRange()).collect(Collectors.joining("\n")));
|
System.out.println("Nodes:\n" + context.getNodes().stream().map(node -> node.getNode().getName() + "@" + node.getRange()).collect(Collectors.joining("\n")));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the command itself by calling the subcommand method associated with the input command node.
|
||||||
|
*
|
||||||
|
* @param context The command context
|
||||||
|
* @return Vanilla command success level (0)
|
||||||
|
*/
|
||||||
private int executeCommand(CommandContext<TP> context) {
|
private int executeCommand(CommandContext<TP> context) {
|
||||||
System.out.println("Execute command");
|
System.out.println("Execute command");
|
||||||
System.out.println("Should be running sync: " + runOnPrimaryThread);
|
System.out.println("Should be running sync: " + runOnPrimaryThread);
|
||||||
|
@ -378,31 +372,6 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getParameterHelp(Method method) {
|
|
||||||
val str = method.getDeclaringClass().getResourceAsStream("/commands.yml");
|
|
||||||
if (str == null)
|
|
||||||
TBMCCoreAPI.SendException("Error while getting command data!", new Exception("Resource not found!"), MainPlugin.Instance);
|
|
||||||
else {
|
|
||||||
YamlConfiguration yc = YamlConfiguration.loadConfiguration(new InputStreamReader(str)); //Generated by ButtonProcessor
|
|
||||||
val ccs = yc.getConfigurationSection(method.getDeclaringClass().getCanonicalName().replace('$', '.'));
|
|
||||||
if (ccs != null) {
|
|
||||||
val cs = ccs.getConfigurationSection(method.getName());
|
|
||||||
if (cs != null) {
|
|
||||||
val mname = cs.getString("method");
|
|
||||||
val params = cs.getString("params");
|
|
||||||
int i = mname.indexOf('('); //Check only the name - the whole method is still stored for backwards compatibility and in case it may be useful
|
|
||||||
if (i != -1 && method.getName().equals(mname.substring(0, i)) && params != null) {
|
|
||||||
return params;
|
|
||||||
} else
|
|
||||||
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method + "' != " + mname + " or params is " + params), MainPlugin.Instance);
|
|
||||||
} else
|
|
||||||
MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + " (cs is null)! Make sure to use 'clean install' when building the project.");
|
|
||||||
} else
|
|
||||||
MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + " (ccs is null)! Make sure to use 'clean install' when building the project.");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
||||||
|
|
|
@ -170,17 +170,6 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
|
||||||
.map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false), true);
|
.map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*@EventHandler
|
|
||||||
public void onTabComplete(TabCompleteEvent event) {
|
|
||||||
try {
|
|
||||||
event.getCompletions().clear(); //Remove player names
|
|
||||||
} catch (UnsupportedOperationException e) {
|
|
||||||
//System.out.println("Tabcomplete: " + event.getBuffer());
|
|
||||||
//System.out.println("First completion: " + event.getCompletions().stream().findFirst().orElse("no completions"));
|
|
||||||
//System.out.println("Listeners: " + Arrays.toString(event.getHandlers().getRegisteredListeners()));
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handleCommand(Command2MCSender sender, String commandline) {
|
public boolean handleCommand(Command2MCSender sender, String commandline) {
|
||||||
return handleCommand(sender, commandline, true);
|
return handleCommand(sender, commandline, true);
|
||||||
|
|
|
@ -7,6 +7,13 @@ import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used as a base class for all the specific command implementations.
|
||||||
|
* It primarily holds information about the command itself and how it should be run, ideally in a programmer-friendly way.
|
||||||
|
* Any inferred and processed information about this command will be stored in the command manager (Command2*).
|
||||||
|
*
|
||||||
|
* @param <TP> The sender's type
|
||||||
|
*/
|
||||||
public abstract class ICommand2<TP extends Command2Sender> {
|
public abstract class ICommand2<TP extends Command2Sender> {
|
||||||
/**
|
/**
|
||||||
* Default handler for commands, can be used to copy the args too.
|
* Default handler for commands, can be used to copy the args too.
|
||||||
|
@ -14,6 +21,7 @@ public abstract class ICommand2<TP extends Command2Sender> {
|
||||||
* @param sender The sender which ran the command
|
* @param sender The sender which ran the command
|
||||||
* @return The success of the command
|
* @return The success of the command
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public boolean def(TP sender) {
|
public boolean def(TP sender) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
package buttondevteam.lib.chat.commands;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
|
import buttondevteam.lib.chat.Command2Sender;
|
||||||
|
import buttondevteam.lib.chat.ICommand2;
|
||||||
|
import lombok.val;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deals with reading the commands.yml file from the plugin. The file is generated by ButtonProcessor at compile-time.
|
||||||
|
* Only used when registering commands.
|
||||||
|
*/
|
||||||
|
public class CommandArgumentHelpManager<TC extends ICommand2<TP>, TP extends Command2Sender> {
|
||||||
|
private ConfigurationSection commandConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the yaml file for the given command class.
|
||||||
|
*
|
||||||
|
* @param command The command object to use
|
||||||
|
*/
|
||||||
|
public CommandArgumentHelpManager(TC command) {
|
||||||
|
val commandClass = command.getClass();
|
||||||
|
// It will load it for each class, but it would be complicated to solve that
|
||||||
|
// Most plugins don't have a lot of command classes anyway
|
||||||
|
try (val str = commandClass.getResourceAsStream("/commands.yml")) {
|
||||||
|
if (str == null) {
|
||||||
|
TBMCCoreAPI.SendException("Error while getting command data!", new Exception("Resource not found!"), MainPlugin.Instance);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
val config = YamlConfiguration.loadConfiguration(new InputStreamReader(str));
|
||||||
|
commandConfig = config.getConfigurationSection(commandClass.getCanonicalName().replace('$', '.'));
|
||||||
|
if (commandConfig == null) {
|
||||||
|
MainPlugin.Instance.getLogger().warning("Failed to get command data for " + commandClass + "! Make sure to use 'clean install' when building the project.");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
TBMCCoreAPI.SendException("Error while getting command data!", e, MainPlugin.Instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a parameter help string for the given subcommand method by reading it from the plugin.
|
||||||
|
*
|
||||||
|
* @param method The subcommand method
|
||||||
|
* @return The parameter part of the usage string for the command
|
||||||
|
*/
|
||||||
|
public String getParameterHelpForMethod(Method method) {
|
||||||
|
val cs = commandConfig.getConfigurationSection(method.getName());
|
||||||
|
if (cs == null) {
|
||||||
|
MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + "! Make sure to use 'clean install' when building the project.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
val mname = cs.getString("method");
|
||||||
|
val params = cs.getString("params");
|
||||||
|
int i = mname.indexOf('('); //Check only the name - the whole method is still stored for backwards compatibility and in case it may be useful
|
||||||
|
if (i != -1 && method.getName().equals(mname.substring(0, i)) && params != null) {
|
||||||
|
return params;
|
||||||
|
} else
|
||||||
|
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method + "' != " + mname + " or params is " + params), MainPlugin.Instance);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue