New command system improvements, broadcast toggles, config fixes #62

Merged
NorbiPeti merged 23 commits from dev into master 2019-03-17 01:27:43 +00:00
10 changed files with 137 additions and 90 deletions
Showing only changes of commit e32805c1fc - Show all commits

View file

@ -2,6 +2,7 @@ package buttondevteam.core;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.Command2.Subcommand;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.ICommand2MC;
@ -13,29 +14,27 @@ import org.bukkit.plugin.Plugin;
import java.util.Optional;
@CommandClass(modOnly = true, helpText = {
"§6---- Component command ----",
"Component command",
"Can be used to enable/disable/list components"
})
public class ComponentCommand extends ICommand2MC {
public ComponentCommand() {
getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg));
getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg), "Plugin not found!");
}
@Subcommand
public boolean enable(CommandSender sender, Plugin plugin, String component) {
if (plugin == null) return respond(sender, "§cPlugin not found!");
plugin.reloadConfig(); //Reload config so the new config values are read - All changes are saved to disk on disable
return enable_disable(sender, plugin, component, true);
}
@Subcommand
public boolean disable(CommandSender sender, Plugin plugin, String component) {
if (plugin == null) return respond(sender, "§cPlugin not found!");
return enable_disable(sender, plugin, component, false);
}
@Subcommand
public boolean list(CommandSender sender, String plugin) {
public boolean list(CommandSender sender, @Command2.OptionalArg String plugin) {
sender.sendMessage("§6List of components:");
Component.getComponents().values().stream().filter(c -> plugin == null || c.getPlugin().getName().equalsIgnoreCase(plugin)) //If plugin is null, don't check
.map(c -> c.getPlugin().getName() + " - " + c.getClass().getSimpleName() + " - " + (c.isEnabled() ? "en" : "dis") + "abled").forEach(sender::sendMessage);

View file

@ -14,7 +14,6 @@ import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.chat.Color;
import buttondevteam.lib.chat.Command2MC;
import buttondevteam.lib.chat.TBMCChatAPI;
import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.player.TBMCPlayer;
@ -30,7 +29,6 @@ import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.RegisteredServiceProvider;
import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@ -42,7 +40,6 @@ import java.util.logging.Logger;
public class MainPlugin extends ButtonPlugin {
public static MainPlugin Instance;
@Nullable
public static Permission permission;
public static boolean Test;
public static Essentials ess;
@ -59,7 +56,8 @@ public class MainPlugin extends ButtonPlugin {
Instance = this;
PluginDescriptionFile pdf = getDescription();
logger = getLogger();
setupPermissions();
if (!setupPermissions())
throw new NullPointerException("No permission plugin found!");
Test = getConfig().getBoolean("test", true);
saveConfig();
Component.registerComponent(this, new PluginUpdaterComponent());
@ -69,7 +67,7 @@ public class MainPlugin extends ButtonPlugin {
Component.registerComponent(this, new MemberComponent());
Component.registerComponent(this, new TownyComponent());
ComponentManager.enableComponents();
Command2MC.registerCommand(new ComponentCommand());
getCommand2MC().registerCommand(new ComponentCommand());
TBMCCoreAPI.RegisterEventsForExceptions(new PlayerListener(), this);
ChromaGamerBase.addConverter(commandSender -> Optional.ofNullable(commandSender instanceof ConsoleCommandSender || commandSender instanceof BlockCommandSender
? TBMCPlayer.getPlayer(new UUID(0, 0), TBMCPlayer.class) : null)); //Console & cmdblocks

View file

@ -3,7 +3,7 @@ package buttondevteam.core;
import buttondevteam.lib.TBMCCommandPreprocessEvent;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.TBMCSystemChatEvent;
import buttondevteam.lib.chat.Command2MC;
import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.player.TBMCPlayerBase;
import lombok.val;
import org.bukkit.Bukkit;
@ -64,7 +64,7 @@ public class PlayerListener implements Listener {
public void onTBMCPreprocess(TBMCCommandPreprocessEvent event) {
if (event.isCancelled()) return;
try {
event.setCancelled(Command2MC.handleCommand(event.getSender(), event.getMessage()));
event.setCancelled(ButtonPlugin.getCommand2MC().handleCommand(event.getSender(), event.getMessage()));
} catch (Exception e) {
TBMCCoreAPI.SendException("Command processing failed for sender '" + event.getSender() + "' and message '" + event.getMessage() + "'", e);
}

View file

@ -1,56 +1,47 @@
package buttondevteam.core.component.members;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.TBMCCommandBase;
import lombok.val;
import buttondevteam.lib.chat.ICommand2MC;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
@CommandClass(modOnly = true, path = "member")
public class MemberCommand extends TBMCCommandBase {
@Override
public boolean OnCommand(CommandSender sender, String alias, String[] args) {
if (args.length < 2)
return false;
final boolean add;
if (args[0].equalsIgnoreCase("add"))
add = true;
else if (args[0].equalsIgnoreCase("remove"))
add = false;
else
return false;
@CommandClass(modOnly = true, path = "member", helpText = { //
"Member command", //
"Add or remove server members.", //
})
public class MemberCommand extends ICommand2MC {
private final MemberComponent component;
public MemberCommand(MemberComponent component) {
getManager().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!");
this.component = component;
}
@Command2.Subcommand
public boolean add(CommandSender sender, OfflinePlayer player) {
return addRemove(sender, player, true);
}
@Command2.Subcommand
public boolean remove(CommandSender sender, OfflinePlayer player) {
return addRemove(sender, player, false);
}
public boolean addRemove(CommandSender sender, OfflinePlayer op, boolean add) {
Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> {
if (MainPlugin.permission == null) {
sender.sendMessage("§cError: No permission plugin found!");
return;
}
val op = Bukkit.getOfflinePlayer(args[1]);
if (!op.hasPlayedBefore()) {
sender.sendMessage("§cCannot find player or haven't played before.");
return;
}
if (add) {
if (MainPlugin.permission.playerAddGroup(null, op, "member"))
sender.sendMessage("§b" + op.getName() + " added as a member!");
else
sender.sendMessage("§cFailed to add " + op.getName() + " as a member!");
} else {
if (MainPlugin.permission.playerRemoveGroup(null, op, "member"))
sender.sendMessage("§b" + op.getName() + " removed as a member!");
else
sender.sendMessage("§bFailed to remove " + op.getName() + " as a member!");
}
if (add ? MainPlugin.permission.playerAddGroup(null, op, component.memberGroup().get())
: MainPlugin.permission.playerRemoveGroup(null, op, component.memberGroup().get()))
sender.sendMessage("§b" + op.getName() + " " + (add ? "added" : "removed") + " as a member!");
else
sender.sendMessage("§cFailed to " + (add ? "add" : "remove") + " " + op.getName() + " as a member!");
});
return true;
}
@Override
public String[] GetHelpText(String alias) {
return new String[]{ //
"06---- Member ----", //
"Add or remove server members.", //
"Usage: /member <add|remove> <player>" //
};
}
}

View file

@ -15,14 +15,14 @@ import java.util.Date;
import static buttondevteam.core.MainPlugin.permission;
public class MemberComponent extends Component implements Listener {
private ConfigData<String> memberGroup() {
ConfigData<String> memberGroup() {
return getConfig().getData("memberGroup", "member");
}
@Override
protected void enable() {
registerListener(this);
registerCommand(new MemberCommand());
registerCommand(new MemberCommand(this));
}
@Override

View file

@ -3,7 +3,7 @@ package buttondevteam.lib.architecture;
import buttondevteam.core.ComponentManager;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException;
import buttondevteam.lib.chat.ICommand2;
import buttondevteam.lib.chat.ICommand2MC;
import buttondevteam.lib.chat.TBMCChatAPI;
import buttondevteam.lib.chat.TBMCCommandBase;
import lombok.Getter;
@ -197,7 +197,7 @@ public abstract class Component {
*
* @param commandBase Custom coded command class
*/
protected final void registerCommand(ICommand2 commandBase) {
protected final void registerCommand(ICommand2MC commandBase) {
ButtonPlugin.getCommand2MC().registerCommand(commandBase);
}

View file

@ -22,11 +22,13 @@ import java.util.stream.Collectors;
/**
* The method name is the subcommand, use underlines (_) to add further subcommands.
* The args may be null if the conversion failed.
* The args may be null if the conversion failed and it's optional.
*/
public abstract class Command2 {
public abstract class Command2<TC extends ICommand2> {
protected Command2() {
}
/**
* TODO: @CommandClass(helpText=...)
* Parameters annotated with this receive all of the remaining arguments
*/
@Target(ElementType.PARAMETER)
@ -46,12 +48,24 @@ public abstract class Command2 {
String[] helpText() default {};
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface OptionalArg {
}
@RequiredArgsConstructor
protected static class SubcommandData<T extends ICommand2> {
public final Method method;
public final T command;
public final String[] helpText;
}
@RequiredArgsConstructor
protected static class ParamConverter<T> {
public final Function<String, T> converter;
public final String errormsg;
}
/**
* Adds a param converter that obtains a specific object from a string parameter.
* The converter may return null.
@ -60,20 +74,29 @@ public abstract class Command2 {
* @param converter The converter to use
* @param <T> The type of the result
*/
public abstract <T> void addParamConverter(Class<T> cl, Function<String, T> converter);
public abstract <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg);
protected <T> void addParamConverter(Class<T> cl, Function<String, T> converter, HashMap<Class<?>, Function<String, ?>> map) {
map.put(cl, converter);
protected <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg, HashMap<Class<?>, ParamConverter<?>> map) {
map.put(cl, new ParamConverter<>(converter, errormsg));
}
public abstract boolean handleCommand(CommandSender sender, String commandLine) throws Exception;
protected <T extends ICommand2> boolean handleCommand(CommandSender sender, String commandline,
HashMap<String, SubcommandData<T>> subcommands, HashMap<Class<?>, Function<String, ?>> paramConverters) throws Exception {
protected boolean handleCommand(CommandSender sender, String commandline,
HashMap<String, SubcommandData<TC>> subcommands,
HashMap<Class<?>, ParamConverter<?>> paramConverters) throws Exception {
for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) {
String subcommand = commandline.substring(0, i).toLowerCase();
SubcommandData sd = subcommands.get(subcommand); //O(1)
if (sd == null) continue; //TODO: This will run each time someone runs any command
SubcommandData<TC> sd = subcommands.get(subcommand); //O(1)
if (sd == null) continue;
if (sd.method == null || sd.command == null) { //Main command not registered, but we have subcommands
sender.sendMessage(sd.helpText);
return true;
}
if (!hasPermission(sender, sd.command)) {
sender.sendMessage("§cYou don't have permission to use this command");
return true;
}
val params = new ArrayList<Object>(sd.method.getParameterCount());
int j = subcommand.length(), pj;
Class<?>[] parameterTypes = sd.method.getParameterTypes();
@ -91,12 +114,18 @@ public abstract class Command2 {
sender.sendMessage("§cYou need to be a " + sendertype.getSimpleName() + " to use this command.");
return true;
}
val paramArr = sd.method.getParameters();
for (int i1 = 1; i1 < parameterTypes.length; i1++) {
Class<?> cl = parameterTypes[i1];
pj = j + 1; //Start index
if (pj == commandline.length() + 1) { //No param given
params.add(null);
continue; //Fill the remaining params with nulls
if (paramArr[i1].isAnnotationPresent(OptionalArg.class)) {
params.add(null);
continue; //Fill the remaining params with nulls
} else {
sender.sendMessage(sd.helpText); //Required param missing
return true;
}
}
j = commandline.indexOf(' ', j + 1); //End index
if (j == -1) //Last parameter
@ -109,7 +138,12 @@ public abstract class Command2 {
val conv = paramConverters.get(cl);
if (conv == null)
throw new Exception("No suitable converter found for parameter type '" + cl.getCanonicalName() + "' for command '" + sd.method.toString() + "'");
params.add(conv.apply(param));
val cparam = conv.converter.apply(param);
if (cparam == null) {
sender.sendMessage(conv.errormsg); //Param conversion failed - ex. plugin not found
return true;
}
params.add(cparam);
}
//System.out.println("Our params: "+params);
val ret = sd.method.invoke(sd.command, params.toArray()); //I FORGOT TO TURN IT INTO AN ARRAY (for a long time)
@ -123,21 +157,26 @@ public abstract class Command2 {
return false; //Didn't handle
} //TODO: Add to the help
public abstract void registerCommand(ICommand2 command);
public abstract void registerCommand(TC command);
protected <T extends ICommand2> void registerCommand(T command, HashMap<String, SubcommandData<T>> subcommands, char commandChar) {
protected void registerCommand(TC command, HashMap<String, SubcommandData<TC>> subcommands, char commandChar) {
val path = command.getCommandPath();
int x = path.indexOf(' ');
val mainPath = commandChar + path.substring(0, x == -1 ? path.length() : x);
//var scmdmap = subcommandStrings.computeIfAbsent(mainPath, k -> new HashSet<>()); //Used to display subcommands
val scmdHelpList = new ArrayList<String>();
Method mainMethod = null;
try { //Register the default handler first so it can be reliably overwritten
val method = command.getClass().getMethod("def", CommandSender.class, String.class);
mainMethod = command.getClass().getMethod("def", CommandSender.class, String.class);
val cc = command.getClass().getAnnotation(CommandClass.class);
var ht = cc == null ? new String[0] : cc.helpText();
String[] both = Arrays.copyOf(ht, ht.length + 1);
both[ht.length] = "Usage: " + commandChar + path; //TODO: Print subcommands
ht = both;
subcommands.put(commandChar + path, new SubcommandData<>(method, command, ht)); //TODO: Disable components when the plugin is disabled
if (ht.length > 0)
ht[0] = "§6---- " + ht[0] + " ----";
scmdHelpList.addAll(Arrays.asList(ht));
scmdHelpList.add("§6Subcommands:");
} catch (Exception e) {
TBMCCoreAPI.SendException("Could not register default handler for command /" + path, e);
} //Continue on
}
for (val method : command.getClass().getMethods()) {
val ann = method.getAnnotation(Subcommand.class);
if (ann != null) {
@ -147,15 +186,22 @@ public abstract class Command2 {
(method.getName().equals("def") ? "" : " " + method.getName().replace('_', ' ').toLowerCase()); //Add method name, unless it's 'def'
ht = getHelpText(method, ht, subcommand);
subcommands.put(subcommand, new SubcommandData<>(method, command, ht)); //Result of the above (def) is that it will show the help text
scmdHelpList.add(subcommand);
}
}
if (mainMethod != null && !subcommands.containsKey(commandChar + path)) //Command specified by the class
subcommands.put(commandChar + path, new SubcommandData<>(mainMethod, command, scmdHelpList.toArray(new String[0])));
if (mainMethod != null && !subcommands.containsKey(mainPath)) //Main command, typically the same as the above
subcommands.put(mainPath, new SubcommandData<>(null, null, scmdHelpList.toArray(new String[0])));
}
private static String[] getHelpText(Method method, String[] ht, String subcommand) { //TODO: helpText[0]="§6---- "+helpText[0]+" ----";
private String[] getHelpText(Method method, String[] ht, String subcommand) {
val str = method.getDeclaringClass().getResourceAsStream("/commands.yml");
if (str == null)
TBMCCoreAPI.SendException("Error while getting command data!", new Exception("Resource not found!"));
else {
if (ht.length > 0)
ht[0] = "§6---- " + ht[0] + " ----";
YamlConfiguration yc = YamlConfiguration.loadConfiguration(new InputStreamReader(str)); //Generated by ButtonProcessor
val ccs = yc.getConfigurationSection(method.getDeclaringClass().getCanonicalName());
if (ccs != null) {
@ -166,7 +212,7 @@ public abstract class Command2 {
val goodname = method.getName() + "(" + Arrays.stream(method.getParameterTypes()).map(cl -> cl.getCanonicalName()).collect(Collectors.joining(",")) + ")";
if (goodname.equals(mname) && params != null) {
String[] both = Arrays.copyOf(ht, ht.length + 1);
both[ht.length] = "Usage: " + subcommand + " " + params;
both[ht.length] = "§6Usage:§r " + subcommand + " " + params;
ht = both;
} else
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method.toString() + "' != " + mname + " or params is " + params));
@ -178,5 +224,5 @@ public abstract class Command2 {
return ht;
}
public abstract boolean hasPermission(CommandSender sender, ICommand2 command);
public abstract boolean hasPermission(CommandSender sender, TC command);
} //TODO: Test support of Player instead of CommandSender

View file

@ -1,24 +1,36 @@
package buttondevteam.lib.chat;
import buttondevteam.core.MainPlugin;
import org.bukkit.command.CommandSender;
import java.util.HashMap;
import java.util.function.Function;
public class Command2MC extends Command2 {
private HashMap<String, SubcommandData<ICommand2>> subcommands = new HashMap<>();
private HashMap<Class<?>, Function<String, ?>> paramConverters = new HashMap<>();
public class Command2MC extends Command2<ICommand2MC> {
private HashMap<String, SubcommandData<ICommand2MC>> subcommands = new HashMap<>();
private HashMap<Class<?>, ParamConverter<?>> paramConverters = new HashMap<>();
@Override
public boolean handleCommand(CommandSender sender, String commandLine) throws Exception {
return handleCommand(sender, commandLine, subcommands, paramConverters);
}
public void registerCommand(ICommand2 command) {
@Override
public void registerCommand(ICommand2MC command) {
registerCommand(command, subcommands, '/');
}
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter) {
addParamConverter(cl, converter, paramConverters);
@Override
public boolean hasPermission(CommandSender sender, ICommand2MC command) {
return MainPlugin.permission.has(sender, "thorpe.command." + command.getCommandPath().replace(' ', '.'));
}
/**
* Automatically colors the message red.
* {@see super#addParamConverter}
*/
@Override
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg) {
addParamConverter(cl, converter, "§c" + errormsg, paramConverters);
}
}

View file

@ -38,7 +38,8 @@ public @interface CommandClass {
boolean excludeFromPath() default false;
/**
* The help text to show for the players. A usage message will be also shown below it.
* The help text to show for the players. A usage message will be also shown below it.<br>
* <b>The fist line will be converted to a header.</b>
*
* @return The help text
*/

View file

@ -31,9 +31,9 @@ public abstract class ICommand2 {
private final String path;
@Getter
private final Command2 manager;
private final Command2<?> manager; //TIL that if I use a raw type on a variable then none of the type args will work (including what's defined on a method, not on the type)
public ICommand2(Command2 manager) {
public <T extends ICommand2> ICommand2(Command2<T> manager) {
path = getcmdpath();
this.manager = manager;
}