Implemented the auto command help.

The help command is a default subcommand of every command. It will
display the usage info for the command and any subcommands.
This commit is contained in:
Tastybento 2017-12-23 21:35:10 -08:00
parent eeb4e2a9ee
commit f8ac04a779
17 changed files with 235 additions and 131 deletions

View File

@ -16,6 +16,7 @@ import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.entity.Player;
import us.tastybento.bskyblock.BSkyBlock;
import us.tastybento.bskyblock.api.events.command.CommandEvent;
import us.tastybento.bskyblock.database.managers.PlayersManager;
import us.tastybento.bskyblock.database.managers.island.IslandsManager;
import us.tastybento.bskyblock.util.Util;
@ -27,14 +28,15 @@ import us.tastybento.bskyblock.util.Util;
*/
public abstract class CompositeCommand extends Command implements PluginIdentifiableCommand, BSBCommand {
private static final boolean DEBUG = true;
private static final boolean DEBUG = false;
private final int level;
private boolean onlyPlayer = false;
private final CompositeCommand parent;
private final int level;
private String permission = "";
public BSkyBlock plugin = BSkyBlock.getPlugin();
private Map<String, CompositeCommand> subCommands;
private String usage;
/**
* Sub-command constructor
@ -50,22 +52,15 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
parent.getSubCommands().put(label, this);
this.setAliases(new ArrayList<>(Arrays.asList(aliases)));
this.subCommands = new LinkedHashMap<>();
setUsage("");
if (!label.equals("help"))
new DefaultHelpCommand(this);
this.setup();
if (DEBUG)
Bukkit.getLogger().info("DEBUG: registering command " + label);
}
/**
* @return this command's sub-level. Top level is 0.
* Every time a command registers with a parent, their level will be set.
*/
protected int getLevel() {
return level;
}
/**
* This is the top-level command constructor for commands that have no parent.
* @param label - string for this command
@ -76,12 +71,15 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
this.setDescription(description);
this.setAliases(new ArrayList<>(Arrays.asList(aliases)));
this.parent = null;
setUsage("");
this.level = 0; // Top level
this.subCommands = new LinkedHashMap<>();
if (!label.equals("help"))
new DefaultHelpCommand(this);
this.setup();
}
/*
* This method deals with the command execution. It traverses the tree of
* subcommands until it finds the right object and then runs execute on it.
@ -105,10 +103,59 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
user.sendMessage("general.errors.no-permission");
return true;
}
// Fire an event to see if this command should be cancelled
CommandEvent event = CommandEvent.builder()
.setCommand(this)
.setLabel(label)
.setSender(sender)
.setArgs(args)
.build();
if (event.isCancelled()) {
return true;
}
// Execute and trim args
return cmd.execute(user, Arrays.asList(args).subList(cmd.level, args.length));
}
/**
* Get the current composite command based on the arguments
* @param args
* @return the current composite command based on the arguments
*/
private CompositeCommand getCommandFromArgs(String[] args) {
CompositeCommand subCommand = this;
// Run through any arguments
if (DEBUG)
Bukkit.getLogger().info("DEBUG: Running through args: " + args.toString());
if (args.length > 0) {
for (int i = 0; i < args.length; i++) {
if (DEBUG)
Bukkit.getLogger().info("DEBUG: Argument " + i);
// get the subcommand corresponding to the arg
if (subCommand.hasSubCommmands()) {
if (DEBUG)
Bukkit.getLogger().info("DEBUG: This command has subcommands");
if (subCommand.hasSubCommand(args[i])) {
// Step down one
subCommand = subCommand.getSubCommand(args[i]);
if (DEBUG)
Bukkit.getLogger().info("DEBUG: Moved to " + subCommand.getLabel());
} else {
return subCommand;
}
} else {
// We are at the end of the walk
if (DEBUG)
Bukkit.getLogger().info("DEBUG: End of traversal");
return subCommand;
}
// else continue the loop
}
}
return subCommand;
}
/**
* @return IslandsManager
*/
@ -116,6 +163,14 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
return plugin.getIslands();
}
/**
* @return this command's sub-level. Top level is 0.
* Every time a command registers with a parent, their level will be set.
*/
protected int getLevel() {
return level;
}
/**
* @param user
* @return set of UUIDs of all team members
@ -147,7 +202,6 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
public BSkyBlock getPlugin() {
return plugin;
}
/**
* Returns the CompositeCommand object refering to this command label
* @param label - command label or alias
@ -160,24 +214,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
}
return null;
}
/**
* Recursively obtain a list of all sub command help references
* TODO: use this in conjunction with a user's request for help
* @return a list of this command and all sub commands help references
*/
public List<String> getSubCommandHelp() {
return getSubCommandHelp("");
}
private List<String> getSubCommandHelp(String helpRef) {
CompositeCommand subCommand = this;
List<String> result = new ArrayList<>();
result.add(helpRef + " " + getDescription());
while (subCommand.hasSubCommmands()) {
result.addAll(subCommand.getSubCommandList(getDescription()));
}
return result;
}
/**
* Recursively obtain a list of all sub commands
* @return a list of this command and all sub commands
@ -186,6 +223,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
return getSubCommandList("");
}
private List<String> getSubCommandList(String label) {
CompositeCommand subCommand = this;
List<String> result = new ArrayList<>();
@ -197,6 +235,9 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
}
/**
* @return Map of sub commands for this command
*/
public Map<String, CompositeCommand> getSubCommands() {
return subCommands;
}
@ -210,6 +251,34 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
return plugin.getIslands().getTeamLeader(user.getUniqueId());
}
@Override
public String getUsage() {
return usage;
}
@Override
public Command setUsage(String usage) {
// Go up the chain
CompositeCommand parent = this.getParent();
this.usage = this.getLabel() + " " + usage;
while (parent != null) {
this.usage = parent.getLabel() + " " + this.usage;
parent = parent.getParent();
}
this.usage = "/" + this.usage;
this.usage = this.usage.trim();
return this;
}
/**
* Get usage for sub commands
* @param subCommands
* @return
*/
public String getUsage(String... subCommands) {
CompositeCommand subCommand = this.getCommandFromArgs(subCommands);
return subCommand.getUsage();
}
/**
* @param subCommand
@ -219,7 +288,6 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
return subCommands.containsKey(subCommand);
}
/**
* @return true if this command has subcommands
*/
@ -247,6 +315,16 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
return (user.getPlayer() instanceof Player);
}
public void setOnlyPlayer(boolean onlyPlayer) {
this.onlyPlayer = onlyPlayer;
}
@Override
public void setPermission(String permission) {
this.permission = permission;
}
@Override
public List<String> tabComplete(final CommandSender sender, final String alias, final String[] args) {
List<String> options = new ArrayList<>();
@ -299,53 +377,4 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
}
return Util.tabLimit(options, lastArg);
}
/**
* Get the current composite command based on the arguments
* @param args
* @return the current composite command based on the arguments
*/
private CompositeCommand getCommandFromArgs(String[] args) {
CompositeCommand subCommand = this;
// Run through any arguments
if (DEBUG)
Bukkit.getLogger().info("DEBUG: Running through args: " + args.toString());
if (args.length > 0) {
for (int i = 0; i <= args.length; i++) {
if (DEBUG)
Bukkit.getLogger().info("DEBUG: Argument " + i);
// get the subcommand corresponding to the arg
if (subCommand.hasSubCommmands()) {
if (DEBUG)
Bukkit.getLogger().info("DEBUG: This command has subcommands");
if (subCommand.hasSubCommand(args[i])) {
// Step down one
subCommand = subCommand.getSubCommand(args[i]);
if (DEBUG)
Bukkit.getLogger().info("DEBUG: Moved to " + subCommand.getLabel());
} else {
return subCommand;
}
} else {
// We are at the end of the walk
if (DEBUG)
Bukkit.getLogger().info("DEBUG: End of traversal");
return subCommand;
}
// else continue the loop
}
}
return subCommand;
}
public void setOnlyPlayer(boolean onlyPlayer) {
this.onlyPlayer = onlyPlayer;
}
@Override
public void setPermission(String permission) {
this.permission = permission;
}
}

View File

@ -0,0 +1,52 @@
package us.tastybento.bskyblock.api.commands;
import java.util.List;
/**
* Adds a default help to every command that will show the usage of the command
* and the usage of any subcommands that the command has.
* @author ben
*
*/
public class DefaultHelpCommand extends CompositeCommand {
private CompositeCommand parent;
public DefaultHelpCommand(CompositeCommand parent) {
super(parent, "help");
this.parent = parent;
}
@Override
public boolean execute(User user, List<String> args) {
if (args.isEmpty()) {
// Show the top level help
if (user.isPlayer()) {
// Player. Check perms
if (user.hasPermission(parent.getPermission()) && user.hasPermission(parent.getPermission())) {
user.sendMessage(parent.getUsage());
}
} else if (!parent.isOnlyPlayer() && !parent.isOnlyPlayer()) {
// Console. Only show if it is a console command
user.sendMessage(parent.getUsage());
}
// Run through any subcommands
for (CompositeCommand subCommand : parent.getSubCommands().values()) {
// Ignore the help command
if (!subCommand.getLabel().equals("help")) {
if (user.isPlayer()) {
// Player. Check perms
if (user.hasPermission(parent.getPermission()) && user.hasPermission(subCommand.getPermission())) {
user.sendMessage(subCommand.getUsage());
}
} else if (!subCommand.isOnlyPlayer()) {
// Console
user.sendMessage(subCommand.getUsage());
}
}
}
}
return true;
}
}

View File

@ -8,9 +8,9 @@ import us.tastybento.bskyblock.commands.island.IslandAboutCommand;
import us.tastybento.bskyblock.commands.island.IslandCreateCommand;
import us.tastybento.bskyblock.commands.island.IslandGoCommand;
import us.tastybento.bskyblock.commands.island.IslandResetCommand;
import us.tastybento.bskyblock.commands.island.IslandResetnameCommand;
import us.tastybento.bskyblock.commands.island.IslandSethomeCommand;
import us.tastybento.bskyblock.commands.island.IslandSetnameCommand;
import us.tastybento.bskyblock.commands.island.IslandSethomeCommand;
import us.tastybento.bskyblock.commands.island.IslandResetnameCommand;
import us.tastybento.bskyblock.commands.island.teams.IslandInviteAcceptCommand;
import us.tastybento.bskyblock.commands.island.teams.IslandInviteRejectCommand;
import us.tastybento.bskyblock.commands.island.teams.IslandLeaveCommand;
@ -24,6 +24,8 @@ public class IslandCommand extends CompositeCommand {
public IslandCommand() {
super(Settings.ISLANDCOMMAND, "is");
this.setUsage("island.usage");
this.setOnlyPlayer(true);
}
/* (non-Javadoc)
@ -38,9 +40,9 @@ public class IslandCommand extends CompositeCommand {
new IslandCreateCommand(this);
new IslandGoCommand(this);
new IslandResetCommand(this);
new IslandResetnameCommand(this);
new IslandSethomeCommand(this);
new IslandSetnameCommand(this);
new IslandSethomeCommand(this);
new IslandResetnameCommand(this);
// Team commands
new IslandTeamCommand(this);
new IslandTeamInviteCommand(this);

View File

@ -14,6 +14,7 @@ public class IslandAboutCommand extends CompositeCommand {
*/
public IslandAboutCommand(CompositeCommand islandCommand) {
super(islandCommand, "about", "ab");
this.setUsage("island.about.usage");
}
@Override

View File

@ -23,6 +23,7 @@ public class IslandCreateCommand extends CompositeCommand {
super(islandCommand, "create", "auto");
this.setPermission(Settings.PERMPREFIX + "island.create");
this.setOnlyPlayer(true);
this.setUsage("island.create.usage");
}
/* (non-Javadoc)

View File

@ -24,6 +24,7 @@ public class IslandGoCommand extends CompositeCommand {
super(islandCommand, "go", "home", "h");
this.setPermission(Settings.PERMPREFIX + "island.home");
this.setOnlyPlayer(true);
this.setUsage("island.go.usage");
}
/* (non-Javadoc)

View File

@ -5,10 +5,8 @@ package us.tastybento.bskyblock.commands.island;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import us.tastybento.bskyblock.api.commands.CompositeCommand;
import us.tastybento.bskyblock.api.commands.User;
@ -21,10 +19,10 @@ import us.tastybento.bskyblock.config.Settings;
public class IslandResetnameCommand extends CompositeCommand {
public IslandResetnameCommand(CompositeCommand command) {
super(command, "setname");
super(command, "resetname");
this.setPermission(Settings.PERMPREFIX + "island.name");
this.setOnlyPlayer(true);
this.setUsage("island.setname.usage");
this.setUsage("island.invite.accept.usage");
}
@ -33,41 +31,19 @@ public class IslandResetnameCommand extends CompositeCommand {
*/
@Override
public boolean execute(User user, List<String> args) {
Player player = user.getPlayer();
UUID playerUUID = player.getUniqueId();
UUID playerUUID = user.getUniqueId();
if (!getIslands().hasIsland(playerUUID)) {
user.sendMessage("general.errors.no-island");
user.sendMessage(ChatColor.RED + "general.errors.no-island");
return true;
}
if (!getIslands().isOwner(playerUUID)) {
user.sendMessage("general.errors.not-leader");
user.sendMessage(ChatColor.RED + "general.errors.not-leader");
return true;
}
// Explain command
if (args.isEmpty()) {
user.sendMessage(getUsage());
return true;
}
// Naming the island - join all the arguments with spaces.
String name = args.stream().collect(Collectors.joining( " " ));
// Check if the name isn't too short or too long
if (name.length() < Settings.nameMinLength) {
user.sendMessage("general.errors.too-short", "[length]", String.valueOf(Settings.nameMinLength));
return true;
}
if (name.length() > Settings.nameMaxLength) {
user.sendMessage("general.errors.too-long", "[length]", String.valueOf(Settings.nameMaxLength));
return true;
}
// Set the name
if (!player.hasPermission(Settings.PERMPREFIX + "island.name.format"))
getIslands().getIsland(player.getUniqueId()).setName(ChatColor.translateAlternateColorCodes('&', name));
else getIslands().getIsland(playerUUID).setName(name);
// Resets the island name
getIslands().getIsland(playerUUID).setName(null);
user.sendMessage("general.success");
return true;

View File

@ -14,6 +14,7 @@ public class IslandSethomeCommand extends CompositeCommand {
super(command, "sethome");
this.setPermission(Settings.PERMPREFIX + "island.sethome");
this.setOnlyPlayer(true);
this.setUsage("island.sethome.usage");
}
@Override

View File

@ -5,8 +5,10 @@ package us.tastybento.bskyblock.commands.island;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import us.tastybento.bskyblock.api.commands.CompositeCommand;
import us.tastybento.bskyblock.api.commands.User;
@ -19,9 +21,10 @@ import us.tastybento.bskyblock.config.Settings;
public class IslandSetnameCommand extends CompositeCommand {
public IslandSetnameCommand(CompositeCommand command) {
super(command, "resetname");
super(command, "setname");
this.setPermission(Settings.PERMPREFIX + "island.name");
this.setOnlyPlayer(true);
this.setUsage("island.setname.usage");
}
@ -30,19 +33,41 @@ public class IslandSetnameCommand extends CompositeCommand {
*/
@Override
public boolean execute(User user, List<String> args) {
UUID playerUUID = user.getUniqueId();
Player player = user.getPlayer();
UUID playerUUID = player.getUniqueId();
if (!getIslands().hasIsland(playerUUID)) {
user.sendMessage(ChatColor.RED + "general.errors.no-island");
user.sendMessage("general.errors.no-island");
return true;
}
if (!getIslands().isOwner(playerUUID)) {
user.sendMessage(ChatColor.RED + "general.errors.not-leader");
user.sendMessage("general.errors.not-leader");
return true;
}
// Resets the island name
getIslands().getIsland(playerUUID).setName(null);
// Explain command
if (args.isEmpty()) {
user.sendMessage(getUsage());
return true;
}
// Naming the island - join all the arguments with spaces.
String name = args.stream().collect(Collectors.joining( " " ));
// Check if the name isn't too short or too long
if (name.length() < Settings.nameMinLength) {
user.sendMessage("general.errors.too-short", "[length]", String.valueOf(Settings.nameMinLength));
return true;
}
if (name.length() > Settings.nameMaxLength) {
user.sendMessage("general.errors.too-long", "[length]", String.valueOf(Settings.nameMaxLength));
return true;
}
// Set the name
if (!player.hasPermission(Settings.PERMPREFIX + "island.name.format"))
getIslands().getIsland(player.getUniqueId()).setName(ChatColor.translateAlternateColorCodes('&', name));
else getIslands().getIsland(playerUUID).setName(name);
user.sendMessage("general.success");
return true;

View File

@ -21,6 +21,7 @@ public class IslandInviteAcceptCommand extends AbstractIslandTeamCommand {
super(islandCommand, "accept");
this.setPermission(Settings.PERMPREFIX + "island.team");
this.setOnlyPlayer(true);
this.setUsage("island.invite.accept.usage");
}
@Override

View File

@ -16,6 +16,7 @@ public class IslandInviteRejectCommand extends AbstractIslandTeamCommand {
super(islandCommand, "reject");
this.setPermission(Settings.PERMPREFIX + "island.team");
this.setOnlyPlayer(true);
this.setUsage("island.invite.reject.usage");
}
@Override

View File

@ -12,6 +12,7 @@ public class IslandLeaveCommand extends AbstractIslandTeamCommand {
super(islandCommand, "leave");
this.setPermission(Settings.PERMPREFIX + "island.team");
this.setOnlyPlayer(true);
this.setUsage("island.leave.usage");
}

View File

@ -23,6 +23,7 @@ public class IslandTeamCommand extends AbstractIslandTeamCommand {
super(islandCommand, "team");
this.setPermission(Settings.PERMPREFIX + "island.team");
this.setOnlyPlayer(true);
this.setUsage("island.team.usage");
}

View File

@ -25,6 +25,7 @@ public class IslandTeamInviteCommand extends AbstractIslandTeamCommand {
super(islandTeamCommand, "invite");
this.setPermission(Settings.PERMPREFIX + "island.team");
this.setOnlyPlayer(true);
this.setUsage("island.team.invite");
}
@Override

View File

@ -24,6 +24,7 @@ public class IslandTeamPromoteCommand extends AbstractIslandTeamCommand {
super(islandCommand, "promote", "makeleader");
this.setPermission(Settings.PERMPREFIX + "island.team");
this.setOnlyPlayer(true);
this.setUsage("island.team.promote.usage");
}
@Override

View File

@ -20,6 +20,7 @@ public class IslandTeamUninviteCommand extends AbstractIslandTeamCommand {
super(islandCommand, "uninvite");
this.setPermission(Settings.PERMPREFIX + "island.team");
this.setOnlyPlayer(true);
this.setUsage("island.team.uninvite.usage");
}
@Override

View File

@ -52,8 +52,7 @@ public class TestIslandCommand {
Server server = mock(Server.class);
Mockito.when(server.getLogger()).thenReturn(Logger.getAnonymousLogger());
Mockito.when(server.getWorld("world")).thenReturn(world);
Mockito.when(server.getVersion()).thenReturn("TestTestMocking");
Mockito.when(server.getVersion()).thenReturn("TestTestMocking");
Mockito.when(server.getVersion()).thenReturn("BSB_Mocking");
Bukkit.setServer(server);
Mockito.when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger());
sender = mock(CommandSender.class);
@ -97,22 +96,22 @@ public class TestIslandCommand {
}
}
String[] args = {""};
assertEquals(Arrays.asList("sub1","sub2"), testCommand.tabComplete(player, "test", args));
assertNotSame(Arrays.asList("sub1","sub2"), testCommand.tabComplete(sender, "test", args));
assertEquals(Arrays.asList("help", "sub1","sub2"), testCommand.tabComplete(player, "test", args));
assertNotSame(Arrays.asList("help", "sub1","sub2"), testCommand.tabComplete(sender, "test", args));
args[0] = "su";
assertEquals(Arrays.asList("sub1","sub2"), testCommand.tabComplete(player, "test", args));
args[0] = "d";
assertNotSame(Arrays.asList("sub1","sub2"), testCommand.tabComplete(player, "test", args));
assertNotSame(Arrays.asList("help", "sub1","sub2"), testCommand.tabComplete(player, "test", args));
args[0] = "sub1";
assertEquals(Arrays.asList(), testCommand.tabComplete(player, "test", args));
String[] args2 = {"sub2",""};
assertEquals(Arrays.asList("subsub"), testCommand.tabComplete(player, "test", args2));
assertEquals(Arrays.asList("help", "subsub"), testCommand.tabComplete(player, "test", args2));
args2[1] = "s";
assertEquals(Arrays.asList("subsub"), testCommand.tabComplete(player, "test", args2));
String[] args3 = {"sub2","subsub", ""};
assertEquals(Arrays.asList("subsubsub"), testCommand.tabComplete(player, "test", args3));
assertEquals(Arrays.asList("help", "subsubsub"), testCommand.tabComplete(player, "test", args3));
// Test for overridden tabcomplete
assertEquals(Arrays.asList(new String[] {"Florian", "Ben", "Bill", "Ted"}),
assertEquals(Arrays.asList(new String[] {"Florian", "Ben", "Bill", "Ted", "help"}),
testCommand.tabComplete(player, "test", new String[] {"sub2", "subsub", "subsubsub", ""}));
// Test for partial word
assertEquals(Arrays.asList(new String[] {"Ben", "Bill"}),
@ -128,12 +127,22 @@ public class TestIslandCommand {
assertFalse(testCommand.execute(player, "test", new String[] {"sub2", "subsub", "subsubsub", "ben", "100"}));
assertTrue(testCommand.execute(player, "test", new String[] {"sub2", "subsub", "subsubsub", "ben", "100", "today"}));
// Usage tests
assertEquals("/test test.usage", testCommand.getUsage());
assertEquals("/test test.usage", testCommand.getUsage("sdfsd"));
assertEquals("/test sub1 sub.usage", testCommand.getUsage("sub1"));
// If the usage has not been defined, then it just shows the command
assertEquals("/test sub2 subsub", testCommand.getUsage("sub2", "subsub"));
// Test help
//assertTrue(testCommand.execute(player, "test", new String[] {"help"}));
}
private class TestCommand extends CompositeCommand {
public TestCommand() {
super("test", "t", "tt");
this.setUsage("test.usage");
}
@Override
@ -154,7 +163,7 @@ public class TestIslandCommand {
public TestSubCommand(CompositeCommand parent) {
super(parent, "sub1", "subone");
this.setUsage("sub.usage");
}
@Override