645 lines
21 KiB
Java
645 lines
21 KiB
Java
package us.tastybento.bskyblock.api.commands;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.HashMap;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.logging.Logger;
|
|
import java.util.stream.Collectors;
|
|
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.World;
|
|
import org.bukkit.command.Command;
|
|
import org.bukkit.command.CommandSender;
|
|
import org.bukkit.command.PluginIdentifiableCommand;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.scheduler.BukkitTask;
|
|
|
|
import us.tastybento.bskyblock.BSkyBlock;
|
|
import us.tastybento.bskyblock.Settings;
|
|
import us.tastybento.bskyblock.api.addons.Addon;
|
|
import us.tastybento.bskyblock.api.events.command.CommandEvent;
|
|
import us.tastybento.bskyblock.api.localization.TextVariables;
|
|
import us.tastybento.bskyblock.api.user.User;
|
|
import us.tastybento.bskyblock.managers.IslandWorldManager;
|
|
import us.tastybento.bskyblock.managers.IslandsManager;
|
|
import us.tastybento.bskyblock.managers.PlayersManager;
|
|
import us.tastybento.bskyblock.util.Util;
|
|
|
|
/**
|
|
* BSB composite command
|
|
* @author tastybento
|
|
* @author Poslovitch
|
|
*/
|
|
public abstract class CompositeCommand extends Command implements PluginIdentifiableCommand, BSBCommand {
|
|
|
|
private final BSkyBlock plugin;
|
|
|
|
/**
|
|
* True if the command is for the player only (not for the console)
|
|
*/
|
|
private boolean onlyPlayer = false;
|
|
/**
|
|
* The parameters string for this command. It is the commands followed by a locale reference.
|
|
*/
|
|
private String parameters = "";
|
|
/**
|
|
* The parent command to this one. If this is a top-level command it will be empty.
|
|
*/
|
|
protected final CompositeCommand parent;
|
|
/**
|
|
* The permission required to execute this command
|
|
*/
|
|
private String permission = "";
|
|
/**
|
|
* This is the command level. 0 is the top, 1 is the first level sub command.
|
|
*/
|
|
private final int subCommandLevel;
|
|
/**
|
|
* Map of sub commands
|
|
*/
|
|
private Map<String, CompositeCommand> subCommands;
|
|
|
|
/**
|
|
* Map of aliases for subcommands
|
|
*/
|
|
private Map<String, CompositeCommand> subCommandAliases;
|
|
/**
|
|
* The command chain from the very top, e.g., island team promote
|
|
*/
|
|
private String usage;
|
|
|
|
/**
|
|
* The prefix to be used in this command
|
|
*/
|
|
private String permissionPrefix = "";
|
|
|
|
/**
|
|
* The world that this command operates in. This is an overworld and will cover any associated nether or end
|
|
* If the world value does not exist, then the command is general across worlds
|
|
*/
|
|
private World world;
|
|
|
|
/**
|
|
* The addon creating this command, if any
|
|
*/
|
|
private Addon addon;
|
|
|
|
/**
|
|
* The top level label
|
|
*/
|
|
private String topLabel = "";
|
|
|
|
private static Map<User, Confirmer> toBeConfirmed = new HashMap<>();
|
|
|
|
/**
|
|
* Top level command
|
|
* @param addon - addon creating the command
|
|
* @param label - string for this command
|
|
* @param aliases - aliases
|
|
*/
|
|
public CompositeCommand(Addon addon, String label, String... aliases) {
|
|
super(label);
|
|
this.addon = addon;
|
|
this.topLabel = label;
|
|
this.plugin = BSkyBlock.getInstance();
|
|
setAliases(new ArrayList<>(Arrays.asList(aliases)));
|
|
parent = null;
|
|
setUsage("");
|
|
subCommandLevel = 0; // Top level
|
|
subCommands = new LinkedHashMap<>();
|
|
subCommandAliases = new LinkedHashMap<>();
|
|
// Register command if it is not already registered
|
|
if (plugin.getCommand(label) == null) {
|
|
plugin.getCommandsManager().registerCommand(this);
|
|
}
|
|
setup();
|
|
if (!getSubCommand("help").isPresent() && !label.equals("help")) {
|
|
new DefaultHelpCommand(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is the top-level command constructor for commands that have no parent.
|
|
* @param label - string for this command
|
|
* @param aliases - aliases for this command
|
|
*/
|
|
public CompositeCommand(String label, String... aliases) {
|
|
this((Addon)null, label, aliases);
|
|
}
|
|
|
|
/**
|
|
* Sub-command constructor
|
|
* @param parent - the parent composite command
|
|
* @param label - string label for this subcommand
|
|
* @param aliases - aliases for this subcommand
|
|
*/
|
|
public CompositeCommand(CompositeCommand parent, String label, String... aliases) {
|
|
super(label);
|
|
this.topLabel = parent.getTopLabel();
|
|
this.plugin = BSkyBlock.getInstance();
|
|
this.parent = parent;
|
|
subCommandLevel = parent.getLevel() + 1;
|
|
// Add this sub-command to the parent
|
|
parent.getSubCommands().put(label, this);
|
|
setAliases(new ArrayList<>(Arrays.asList(aliases)));
|
|
subCommands = new LinkedHashMap<>();
|
|
subCommandAliases = new LinkedHashMap<>();
|
|
// Add aliases to the parent for this command
|
|
for (String alias : aliases) {
|
|
parent.getSubCommandAliases().put(alias, this);
|
|
}
|
|
setUsage("");
|
|
// Inherit permission prefix
|
|
this.permissionPrefix = parent.getPermissionPrefix();
|
|
// Inherit world
|
|
this.world = parent.getWorld();
|
|
setup();
|
|
// If this command does not define its own help class, then use the default help command
|
|
if (!getSubCommand("help").isPresent() && !label.equals("help")) {
|
|
new DefaultHelpCommand(this);
|
|
}
|
|
|
|
}
|
|
/*
|
|
* 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.
|
|
*/
|
|
@Override
|
|
public boolean execute(CommandSender sender, String label, String[] args) {
|
|
// Get the User instance for this sender
|
|
User user = User.getInstance(sender);
|
|
CompositeCommand cmd = getCommandFromArgs(args);
|
|
// Check for console and permissions
|
|
if (cmd.onlyPlayer && !(sender instanceof Player)) {
|
|
user.sendMessage("general.errors.use-in-game");
|
|
return false;
|
|
}
|
|
// Check perms, but only if this isn't the console
|
|
if ((sender instanceof Player) && !sender.isOp() && !cmd.getPermission().isEmpty() && !sender.hasPermission(cmd.getPermission())) {
|
|
user.sendMessage("general.errors.no-permission");
|
|
user.sendMessage("general.errors.you-need", TextVariables.PERMISSION, cmd.getPermission());
|
|
return false;
|
|
}
|
|
// 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 false;
|
|
}
|
|
// Execute and trim args
|
|
return cmd.execute(user, (cmd.subCommandLevel > 0) ? args[cmd.subCommandLevel-1] : label, Arrays.asList(args).subList(cmd.subCommandLevel, args.length));
|
|
}
|
|
|
|
/**
|
|
* Get the current composite command based on the arguments
|
|
* @param args - arguments
|
|
* @return the current composite command based on the arguments
|
|
*/
|
|
private CompositeCommand getCommandFromArgs(String[] args) {
|
|
CompositeCommand subCommand = this;
|
|
// Run through any arguments
|
|
for (String arg : args) {
|
|
// get the subcommand corresponding to the arg
|
|
if (subCommand.hasSubCommands()) {
|
|
Optional<CompositeCommand> sub = subCommand.getSubCommand(arg);
|
|
if (!sub.isPresent()) {
|
|
return subCommand;
|
|
}
|
|
// Step down one
|
|
subCommand = sub.orElse(subCommand);
|
|
// Set the label
|
|
subCommand.setLabel(arg);
|
|
} else {
|
|
// We are at the end of the walk
|
|
return subCommand;
|
|
}
|
|
// else continue the loop
|
|
}
|
|
return subCommand;
|
|
}
|
|
|
|
/**
|
|
* Convenience method to get the island manager
|
|
* @return IslandsManager
|
|
*/
|
|
protected IslandsManager getIslands() {
|
|
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 subCommandLevel;
|
|
}
|
|
|
|
/**
|
|
* @return Logger
|
|
*/
|
|
public Logger getLogger() {
|
|
return plugin.getLogger();
|
|
}
|
|
|
|
/**
|
|
* Convenience method to obtain team members
|
|
* @param world - world to check
|
|
* @param user - the User
|
|
* @return set of UUIDs of all team members
|
|
*/
|
|
protected Set<UUID> getMembers(World world, User user) {
|
|
return plugin.getIslands().getMembers(world, user.getUniqueId());
|
|
}
|
|
|
|
public String getParameters() {
|
|
return parameters;
|
|
}
|
|
|
|
/**
|
|
* @return the parent command object
|
|
*/
|
|
public CompositeCommand getParent() {
|
|
return parent;
|
|
}
|
|
|
|
@Override
|
|
public String getPermission() {
|
|
return permission;
|
|
}
|
|
|
|
/**
|
|
* Convenience method to get the player manager
|
|
* @return PlayersManager
|
|
*/
|
|
protected PlayersManager getPlayers() {
|
|
return plugin.getPlayers();
|
|
}
|
|
|
|
@Override
|
|
public BSkyBlock getPlugin() {
|
|
return plugin;
|
|
}
|
|
|
|
/**
|
|
* Get the island worlds manager
|
|
* @return island worlds manager
|
|
*/
|
|
public IslandWorldManager getIWM() {
|
|
return plugin.getIWM();
|
|
}
|
|
/**
|
|
* @return Settings object
|
|
*/
|
|
public Settings getSettings() {
|
|
return plugin.getSettings();
|
|
}
|
|
|
|
/**
|
|
* Returns the CompositeCommand object referring to this command label
|
|
* @param label - command label or alias
|
|
* @return CompositeCommand or null if none found
|
|
*/
|
|
public Optional<CompositeCommand> getSubCommand(String label) {
|
|
label = label.toLowerCase();
|
|
if (subCommands.containsKey(label)) {
|
|
return Optional.ofNullable(subCommands.get(label));
|
|
}
|
|
// Try aliases
|
|
if (subCommandAliases.containsKey(label)) {
|
|
return Optional.ofNullable(subCommandAliases.get(label));
|
|
}
|
|
return Optional.empty();
|
|
}
|
|
|
|
/**
|
|
* @return Map of sub commands for this command
|
|
*/
|
|
public Map<String, CompositeCommand> getSubCommands() {
|
|
return subCommands;
|
|
}
|
|
|
|
/**
|
|
* Returns a map of sub commands for this command.
|
|
* As it needs more calculations to handle the Help subcommand, it is preferable to use {@link #getSubCommands()} when no such distinction is needed.
|
|
* @param ignoreHelp Whether the Help subcommand should not be returned in the map or not.
|
|
* @return Map of sub commands for this command
|
|
* @see #hasSubCommands(boolean)
|
|
*/
|
|
public Map<String, CompositeCommand> getSubCommands(boolean ignoreHelp) {
|
|
if (ignoreHelp && getSubCommand("help").isPresent()) {
|
|
Map<String, CompositeCommand> result = subCommands;
|
|
result.remove("help");
|
|
return result;
|
|
}
|
|
return getSubCommands();
|
|
}
|
|
|
|
/**
|
|
* Convenience method to obtain the user's team leader
|
|
* @param world - world to check
|
|
* @param user - the User
|
|
* @return UUID of player's team leader or null if user has no island
|
|
*/
|
|
protected UUID getTeamLeader(World world, User user) {
|
|
return plugin.getIslands().getTeamLeader(world, user.getUniqueId());
|
|
}
|
|
|
|
@Override
|
|
public String getUsage() {
|
|
return "/" + usage;
|
|
}
|
|
|
|
/**
|
|
* Check if this command has a specific sub command.
|
|
* @param subCommand - sub command
|
|
* @return true if this command has this sub command
|
|
*/
|
|
protected boolean hasSubCommand(String subCommand) {
|
|
return subCommands.containsKey(subCommand) || subCommandAliases.containsKey(subCommand);
|
|
}
|
|
|
|
/**
|
|
* Check if this command has any sub commands.
|
|
* @return true if this command has subcommands
|
|
*/
|
|
protected boolean hasSubCommands() {
|
|
return !subCommands.isEmpty();
|
|
}
|
|
|
|
/**
|
|
* Check if this command has any sub commands.
|
|
* As it needs more calculations to handle the Help subcommand, it is preferable to use {@link #hasSubCommands()} when no such distinction is needed.
|
|
* @param ignoreHelp Whether the Help subcommand should not be taken into account or not.
|
|
* @return true if this command has subcommands
|
|
* @see #getSubCommands(boolean)
|
|
*/
|
|
protected boolean hasSubCommands(boolean ignoreHelp) {
|
|
return !getSubCommands(ignoreHelp).isEmpty();
|
|
}
|
|
|
|
/**
|
|
* Convenience method to check if a user has a team.
|
|
* @param world - the world to check
|
|
* @param user - the User
|
|
* @return true if player is in a team
|
|
*/
|
|
protected boolean inTeam(World world, User user) {
|
|
return plugin.getIslands().inTeam(world, user.getUniqueId());
|
|
}
|
|
|
|
/**
|
|
* Check if this command is only for players.
|
|
* @return true or false
|
|
*/
|
|
public boolean isOnlyPlayer() {
|
|
return onlyPlayer;
|
|
}
|
|
|
|
/**
|
|
* Convenience method to check if a user is a player
|
|
* @param user - the User
|
|
* @return true if sender is a player
|
|
*/
|
|
protected boolean isPlayer(User user) {
|
|
return user.getPlayer() != null;
|
|
}
|
|
|
|
/**
|
|
* Set whether this command is only for players
|
|
* @param onlyPlayer - true if command only for players
|
|
*/
|
|
public void setOnlyPlayer(boolean onlyPlayer) {
|
|
this.onlyPlayer = onlyPlayer;
|
|
}
|
|
|
|
/**
|
|
* Sets the command parameters to be shown in help
|
|
* @param parameters - string of parameters
|
|
*/
|
|
public void setParameters(String parameters) {
|
|
this.parameters = parameters;
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see org.bukkit.command.Command#setPermission(java.lang.String)
|
|
*/
|
|
@Override
|
|
public void setPermission(String permission) {
|
|
this.permission = permissionPrefix + permission;
|
|
}
|
|
|
|
/**
|
|
* Inherits the permission from parent command
|
|
*/
|
|
public void inheritPermission() {
|
|
this.permission = parent.getPermission();
|
|
}
|
|
|
|
/**
|
|
* This creates the full linking chain of commands
|
|
*/
|
|
@Override
|
|
public Command setUsage(String usage) {
|
|
// Go up the chain
|
|
CompositeCommand parentCommand = getParent();
|
|
StringBuilder u = new StringBuilder().append(getLabel()).append(" ").append(usage);
|
|
while (parentCommand != null) {
|
|
u.insert(0, " ");
|
|
u.insert(0, parentCommand.getLabel());
|
|
parentCommand = parentCommand.getParent();
|
|
}
|
|
this.usage = u.toString().trim();
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public List<String> tabComplete(final CommandSender sender, final String alias, final String[] args) {
|
|
List<String> options = new ArrayList<>();
|
|
// Get command object based on args entered so far
|
|
CompositeCommand cmd = getCommandFromArgs(args);
|
|
// Check for console and permissions
|
|
if (cmd.onlyPlayer && !(sender instanceof Player)) {
|
|
return options;
|
|
}
|
|
if (!cmd.getPermission().isEmpty() && !sender.hasPermission(cmd.getPermission()) && !sender.isOp()) {
|
|
return options;
|
|
}
|
|
// Add any tab completion from the subcommand
|
|
options.addAll(cmd.tabComplete(User.getInstance(sender), alias, new LinkedList<>(Arrays.asList(args))).orElse(new ArrayList<>()));
|
|
// Add any sub-commands automatically
|
|
if (cmd.hasSubCommands()) {
|
|
// Check if subcommands are visible to this sender
|
|
for (CompositeCommand subCommand: cmd.getSubCommands().values()) {
|
|
if (sender instanceof Player) {
|
|
// Player
|
|
if (subCommand.getPermission().isEmpty() || sender.hasPermission(subCommand.getPermission()) || sender.isOp()) {
|
|
// Permission is okay
|
|
options.add(subCommand.getLabel());
|
|
}
|
|
} else {
|
|
// Console
|
|
if (!subCommand.onlyPlayer) {
|
|
// Not a player command
|
|
options.add(subCommand.getLabel());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
String lastArg = args.length != 0 ? args[args.length - 1] : "";
|
|
return Util.tabLimit(options, lastArg).stream().sorted().collect(Collectors.toList());
|
|
}
|
|
|
|
/**
|
|
* Show help
|
|
* @param command - command that this help is for
|
|
* @param user - the User
|
|
* @return result of help command or false if no help defined
|
|
*/
|
|
protected boolean showHelp(CompositeCommand command, User user) {
|
|
return command.getSubCommand("help").map(helpCommand -> helpCommand.execute(user, helpCommand.getLabel(), new ArrayList<>())).orElse(false);
|
|
}
|
|
|
|
/**
|
|
* @return the subCommandAliases
|
|
*/
|
|
public Map<String, CompositeCommand> getSubCommandAliases() {
|
|
return subCommandAliases;
|
|
}
|
|
|
|
/**
|
|
* If the permission prefix has been set, will return the prefix plus a trailing dot.
|
|
* @return the permissionPrefix
|
|
*/
|
|
public String getPermissionPrefix() {
|
|
return permissionPrefix;
|
|
}
|
|
|
|
/**
|
|
* Set the permission prefix. This will be added automatically to the permission
|
|
* and will apply to any sub commands too.
|
|
* Do not put a dot on the end of it.
|
|
* @param permissionPrefix the permissionPrefix to set
|
|
*/
|
|
public void setPermissionPrefix(String permissionPrefix) {
|
|
this.permissionPrefix = permissionPrefix + ".";
|
|
}
|
|
|
|
/**
|
|
* The the world that this command applies to.
|
|
* @return the world
|
|
*/
|
|
public World getWorld() {
|
|
if (world == null) {
|
|
plugin.logError(getLabel() + " did not setWorld in setup!");
|
|
}
|
|
return world;
|
|
}
|
|
|
|
/**
|
|
* @param world the world to set
|
|
*/
|
|
public void setWorld(World world) {
|
|
this.world = world;
|
|
}
|
|
|
|
/**
|
|
* @return the addon
|
|
*/
|
|
public Addon getAddon() {
|
|
return addon;
|
|
}
|
|
|
|
/**
|
|
* @return top level label, e.g., island
|
|
*/
|
|
public String getTopLabel() {
|
|
return topLabel;
|
|
}
|
|
|
|
/**
|
|
* Tells user to confirm command by retyping
|
|
* @param user - user
|
|
* @param confirmed - runnable to be executed if confirmed
|
|
*/
|
|
public void askConfirmation(User user, Runnable confirmed) {
|
|
// Check for pending confirmations
|
|
if (toBeConfirmed.containsKey(user)) {
|
|
if (toBeConfirmed.get(user).getTopLabel().equals(getTopLabel()) && toBeConfirmed.get(user).getLabel().equalsIgnoreCase(getLabel())) {
|
|
toBeConfirmed.get(user).getTask().cancel();
|
|
Bukkit.getScheduler().runTask(getPlugin(), toBeConfirmed.get(user).getRunnable());
|
|
toBeConfirmed.remove(user);
|
|
return;
|
|
} else {
|
|
// Player has another outstanding confirmation request that will now be cancelled
|
|
user.sendMessage("general.previous-request-cancelled");
|
|
}
|
|
}
|
|
// Tell user that they need to confirm
|
|
user.sendMessage("general.confirm", "[seconds]", String.valueOf(getSettings().getConfirmationTime()));
|
|
// Set up a cancellation task
|
|
BukkitTask task = Bukkit.getScheduler().runTaskLater(getPlugin(), () -> {
|
|
user.sendMessage("general.request-cancelled");
|
|
toBeConfirmed.remove(user);
|
|
}, getPlugin().getSettings().getConfirmationTime() * 20L);
|
|
|
|
// Add to the global confirmation map
|
|
toBeConfirmed.put(user, new Confirmer(getTopLabel(), getLabel(), confirmed, task));
|
|
}
|
|
|
|
private class Confirmer {
|
|
private final String topLabel;
|
|
private final String label;
|
|
private final Runnable runnable;
|
|
private final BukkitTask task;
|
|
|
|
/**
|
|
* @param label - command label
|
|
* @param runnable - runnable to run when confirmed
|
|
* @param task - task ID to cancel when confirmed
|
|
*/
|
|
Confirmer(String topLabel, String label, Runnable runnable, BukkitTask task) {
|
|
this.topLabel = topLabel;
|
|
this.label = label;
|
|
this.runnable = runnable;
|
|
this.task = task;
|
|
}
|
|
/**
|
|
* @return the topLabel
|
|
*/
|
|
public String getTopLabel() {
|
|
return topLabel;
|
|
}
|
|
/**
|
|
* @return the label
|
|
*/
|
|
public String getLabel() {
|
|
return label;
|
|
}
|
|
/**
|
|
* @return the runnable
|
|
*/
|
|
public Runnable getRunnable() {
|
|
return runnable;
|
|
}
|
|
/**
|
|
* @return the task
|
|
*/
|
|
public BukkitTask getTask() {
|
|
return task;
|
|
}
|
|
|
|
}
|
|
}
|