diff --git a/eco-api/src/main/java/com/willfp/eco/core/EcoPlugin.java b/eco-api/src/main/java/com/willfp/eco/core/EcoPlugin.java index 72266d30..aaef74a3 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/EcoPlugin.java +++ b/eco-api/src/main/java/com/willfp/eco/core/EcoPlugin.java @@ -1,6 +1,7 @@ package com.willfp.eco.core; import com.willfp.eco.core.command.AbstractCommand; +import com.willfp.eco.core.command.impl.BaseCommand; import com.willfp.eco.core.config.ConfigHandler; import com.willfp.eco.core.config.base.ConfigYml; import com.willfp.eco.core.config.base.LangYml; @@ -46,6 +47,7 @@ import java.util.Set; import java.util.logging.Logger; import java.util.stream.Collectors; +@SuppressWarnings({"deprecation", "DeprecatedIsStillUsed"}) public abstract class EcoPlugin extends JavaPlugin { /** * Loaded eco plugins. @@ -394,6 +396,7 @@ public abstract class EcoPlugin extends JavaPlugin { this.getListeners().forEach(listener -> this.getEventManager().registerListener(listener)); this.getCommands().forEach(AbstractCommand::register); + this.getPluginCommands().forEach(BaseCommand::register); this.getScheduler().runLater(this::afterLoad, 1); @@ -549,11 +552,22 @@ public abstract class EcoPlugin extends JavaPlugin { } /** - * The command to be registered. + * The commands to be registered. + * + * @return A list of commands. + * @deprecated Use {@link this#getPluginCommands()} instead. + */ + @Deprecated + public List getCommands() { + return new ArrayList<>(); + } + + /** + * The commands to be registered. * * @return A list of commands. */ - public List getCommands() { + public List getPluginCommands() { return new ArrayList<>(); } diff --git a/eco-api/src/main/java/com/willfp/eco/core/command/AbstractCommand.java b/eco-api/src/main/java/com/willfp/eco/core/command/AbstractCommand.java index 9cb048f6..697f9d7d 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/command/AbstractCommand.java +++ b/eco-api/src/main/java/com/willfp/eco/core/command/AbstractCommand.java @@ -14,6 +14,8 @@ import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.List; +@Deprecated +@SuppressWarnings("DeprecatedIsStillUsed") public abstract class AbstractCommand extends PluginDependent implements CommandExecutor { /** * The name of the command diff --git a/eco-api/src/main/java/com/willfp/eco/core/command/AbstractTabCompleter.java b/eco-api/src/main/java/com/willfp/eco/core/command/AbstractTabCompleter.java index f5b56ccd..357ee69d 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/command/AbstractTabCompleter.java +++ b/eco-api/src/main/java/com/willfp/eco/core/command/AbstractTabCompleter.java @@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.List; +@Deprecated public abstract class AbstractTabCompleter implements TabCompleter { /** * The {@link AbstractCommand} that is tab-completed. diff --git a/eco-api/src/main/java/com/willfp/eco/core/command/CommandBase.java b/eco-api/src/main/java/com/willfp/eco/core/command/CommandBase.java new file mode 100644 index 00000000..ceae47ef --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/command/CommandBase.java @@ -0,0 +1,48 @@ +package com.willfp.eco.core.command; + +import org.jetbrains.annotations.NotNull; + +public interface CommandBase { + /** + * Get command name. + * + * @return The name. + */ + String getName(); + + /** + * Get command permission. + * + * @return The permission. + */ + String getPermission(); + + /** + * If only players can execute the command. + * + * @return If true. + */ + boolean isPlayersOnly(); + + /** + * Add a subcommand to the command. + * + * @param command The subcommand. + * @return The parent command. + */ + CommandBase addSubcommand(@NotNull CommandBase command); + + /** + * Get the handler. + * + * @return The handler. + */ + CommandHandler getHandler(); + + /** + * Get the tab completer. + * + * @return The tab completer. + */ + TabCompleteHandler getTabCompleter(); +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/command/CommandHandler.java b/eco-api/src/main/java/com/willfp/eco/core/command/CommandHandler.java new file mode 100644 index 00000000..3549bb7f --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/command/CommandHandler.java @@ -0,0 +1,18 @@ +package com.willfp.eco.core.command; + +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +@FunctionalInterface +public interface CommandHandler { + /** + * The code to be called on execution. + * + * @param sender The sender. + * @param args The arguments. + */ + void onExecute(@NotNull CommandSender sender, + @NotNull List args); +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/command/TabCompleteHandler.java b/eco-api/src/main/java/com/willfp/eco/core/command/TabCompleteHandler.java new file mode 100644 index 00000000..def2e483 --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/command/TabCompleteHandler.java @@ -0,0 +1,19 @@ +package com.willfp.eco.core.command; + +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +@FunctionalInterface +public interface TabCompleteHandler { + /** + * Handle Tab Completion. + * + * @param sender The sender. + * @param args The arguments. + * @return The tab completion results. + */ + List tabComplete(@NotNull CommandSender sender, + @NotNull List args); +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/command/impl/BaseCommand.java b/eco-api/src/main/java/com/willfp/eco/core/command/impl/BaseCommand.java new file mode 100644 index 00000000..d266c8a4 --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/command/impl/BaseCommand.java @@ -0,0 +1,134 @@ +package com.willfp.eco.core.command.impl; + +import com.willfp.eco.core.EcoPlugin; +import com.willfp.eco.core.command.CommandBase; +import com.willfp.eco.core.command.util.CommandUtils; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.command.TabCompleter; +import org.bukkit.util.StringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public abstract class BaseCommand extends HandledCommand implements CommandExecutor, TabCompleter { + /** + * Create a new command. + *

+ * The command will not be registered until {@link this#register()} is called. + *

+ * The name cannot be the same as an existing command as this will conflict. + * @param name The name used in execution. + * @param permission The permission required to execute the command. + * @param playersOnly If only players should be able to execute this command. + */ + protected BaseCommand(@NotNull final String name, + @NotNull final String permission, + final boolean playersOnly) { + super(name, permission, playersOnly); + } + + /** + * Registers the command with the server, + *

+ * Requires the command name to exist, defined in plugin.yml. + */ + public final void register() { + PluginCommand command = Bukkit.getPluginCommand(this.getName()); + assert command != null; + command.setExecutor(this); + command.setTabCompleter(this); + } + + /** + * Internal implementation used to clean up boilerplate. + * Used for parity with {@link CommandExecutor#onCommand(CommandSender, Command, String, String[])}. + * + * @param sender The executor of the command. + * @param command The bukkit command. + * @param label The name of the executed command. + * @param args The arguments of the command (anything after the physical command name) + * @return If the command was processed by the linked {@link EcoPlugin} + */ + @Override + public final boolean onCommand(@NotNull final CommandSender sender, + @NotNull final Command command, + @NotNull final String label, + @NotNull final String[] args) { + if (!command.getName().equalsIgnoreCase(this.getName())) { + return false; + } + + if (!CommandUtils.canExecute(sender, this)) { + return true; + } + + if (args.length > 0) { + for (CommandBase subcommand : this.getSubcommands()) { + if (subcommand.getName().equalsIgnoreCase(args[0])) { + if (!CommandUtils.canExecute(sender, subcommand)) { + return true; + } + + subcommand.getHandler().onExecute(sender, Arrays.asList(Arrays.copyOfRange(args, 1, args.length))); + + return true; + } + } + } + + this.getHandler().onExecute(sender, Arrays.asList(args)); + + return true; + } + + /** + * Internal implementation used to clean up boilerplate. + * Used for parity with {@link TabCompleter#onTabComplete(CommandSender, Command, String, String[])}. + * + * @param sender The executor of the command. + * @param command The bukkit command. + * @param label The name of the executed command. + * @param args The arguments of the command (anything after the physical command name). + * @return The list of tab-completions. + */ + @Override + public @Nullable List onTabComplete(@NotNull final CommandSender sender, + @NotNull final Command command, + @NotNull final String label, + @NotNull final String[] args) { + if (!command.getName().equalsIgnoreCase(this.getName())) { + return null; + } + + if (!sender.hasPermission(this.getPermission())) { + return null; + } + + if (args.length > 0) { + List completions = new ArrayList<>(); + + StringUtil.copyPartialMatches( + args[0], + this.getSubcommands().stream().map(CommandBase::getName).collect(Collectors.toList()), + completions + ); + + Collections.sort(completions); + + if (!completions.isEmpty()) { + return completions; + } + } + + return this.getTabCompleter().tabComplete(sender, Arrays.asList(args)); + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/command/impl/HandledCommand.java b/eco-api/src/main/java/com/willfp/eco/core/command/impl/HandledCommand.java new file mode 100644 index 00000000..0f4da2ec --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/command/impl/HandledCommand.java @@ -0,0 +1,82 @@ +package com.willfp.eco.core.command.impl; + +import com.willfp.eco.core.command.CommandBase; +import com.willfp.eco.core.command.CommandHandler; +import com.willfp.eco.core.command.TabCompleteHandler; +import lombok.AccessLevel; +import lombok.Getter; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.TabCompleter; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public abstract class HandledCommand implements CommandBase, CommandExecutor, TabCompleter { + /** + * The name of the command. + */ + @Getter + private final String name; + + /** + * The permission required to execute the command. + *

+ * Written out as a string for flexibility with subclasses. + */ + @Getter + private final String permission; + + /** + * Should the command only be allowed to be executed by players? + *

+ * In other worlds, only allowed to be executed by console. + */ + @Getter + private final boolean playersOnly; + + /** + * All subcommands for the command. + */ + @Getter(AccessLevel.PROTECTED) + private final List subcommands; + + /** + * Create a new command. + *

+ * The name cannot be the same as an existing command as this will conflict. + * + * @param name The name used in execution. + * @param permission The permission required to execute the command. + * @param playersOnly If only players should be able to execute this command. + */ + protected HandledCommand(@NotNull final String name, + @NotNull final String permission, + final boolean playersOnly) { + this.name = name; + this.permission = permission; + this.playersOnly = playersOnly; + this.subcommands = new ArrayList<>(); + } + + /** + * Add a subcommand to the command. + * + * @param subcommand The subcommand. + * @return The parent command. + */ + @Override + public final CommandBase addSubcommand(@NotNull final CommandBase subcommand) { + subcommands.add(subcommand); + + return this; + } + + @Override + public abstract CommandHandler getHandler(); + + @Override + public TabCompleteHandler getTabCompleter() { + return (sender, args) -> new ArrayList<>(); + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/command/impl/Subcommand.java b/eco-api/src/main/java/com/willfp/eco/core/command/impl/Subcommand.java new file mode 100644 index 00000000..06fc239f --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/command/impl/Subcommand.java @@ -0,0 +1,30 @@ +package com.willfp.eco.core.command.impl; + +import com.willfp.eco.core.command.CommandBase; +import org.jetbrains.annotations.NotNull; + +public abstract class Subcommand extends HandledCommand { + /** + * Create subcommand. + * + * @param name The subcommand name. + * @param permission The subcommand permission. + * @param playersOnly If the subcommand only works on players. + */ + protected Subcommand(@NotNull final String name, + @NotNull final String permission, + final boolean playersOnly) { + super(name, permission, playersOnly); + } + + /** + * Create subcommand. + * + * @param name The name of the subcommand. + * @param parent The parent command. + */ + protected Subcommand(@NotNull final String name, + @NotNull final CommandBase parent) { + super(name, parent.getPermission(), parent.isPlayersOnly()); + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/command/util/CommandUtils.java b/eco-api/src/main/java/com/willfp/eco/core/command/util/CommandUtils.java new file mode 100644 index 00000000..64f7f86d --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/command/util/CommandUtils.java @@ -0,0 +1,33 @@ +package com.willfp.eco.core.command.util; + +import com.willfp.eco.core.command.CommandBase; +import com.willfp.eco.internal.Internals; +import lombok.experimental.UtilityClass; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +@UtilityClass +public class CommandUtils { + /** + * Check if the sender can execute a command. + * + * @param sender The sender. + * @param command The command. + * @return If possible. Sends messages. + */ + public boolean canExecute(@NotNull final CommandSender sender, + @NotNull final CommandBase command) { + if (command.isPlayersOnly() && !(sender instanceof Player)) { + sender.sendMessage(Internals.getInstance().getPlugin().getLangYml().getMessage("not-player")); + return false; + } + + if (!sender.hasPermission(command.getPermission()) && sender instanceof Player) { + sender.sendMessage(Internals.getInstance().getPlugin().getLangYml().getNoPermission()); + return false; + } + + return true; + } +}