diff --git a/build.gradle.kts b/build.gradle.kts index a21fdfd..7930b0f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -106,14 +106,6 @@ bukkit { authors = listOf("destro174") depend = listOf("Vault") - commands { - register("playershop") { - description = "This is a test command!" - aliases = listOf("shop") - permission = "playershops.command.playershop" - } - } - permissions { register("playershops.admin") { description = "Admin permission for the ${rootProject.name} plugin." diff --git a/src/main/java/com/alttd/playershops/PlayerShops.java b/src/main/java/com/alttd/playershops/PlayerShops.java index f571cef..e65c256 100644 --- a/src/main/java/com/alttd/playershops/PlayerShops.java +++ b/src/main/java/com/alttd/playershops/PlayerShops.java @@ -1,6 +1,6 @@ package com.alttd.playershops; -import com.alttd.playershops.commands.ShopCommand; +import com.alttd.playershops.commands.PlayerShopCommands; import com.alttd.playershops.config.Config; import com.alttd.playershops.config.DatabaseConfig; import com.alttd.playershops.config.MessageConfig; @@ -102,7 +102,7 @@ public class PlayerShops extends JavaPlugin { } private void registerCommands() { - getCommand("playershop").setExecutor(new ShopCommand()); + PlayerShopCommands.registerCommands(); } public void reloadConfigs() { diff --git a/src/main/java/com/alttd/playershops/commands/PlayerShopCommand.java b/src/main/java/com/alttd/playershops/commands/PlayerShopCommand.java new file mode 100644 index 0000000..2d97efe --- /dev/null +++ b/src/main/java/com/alttd/playershops/commands/PlayerShopCommand.java @@ -0,0 +1,141 @@ +package com.alttd.playershops.commands; + +import com.alttd.playershops.commands.subcommands.CheckStockCommand; +import com.alttd.playershops.commands.subcommands.OpenCommand; +import com.alttd.playershops.commands.subcommands.ReloadCommand; +import com.google.common.base.Functions; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import it.unimi.dsi.fastutil.Pair; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionDefault; +import org.bukkit.plugin.PluginManager; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.stream.Collectors; + +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.format.NamedTextColor.RED; + +public class PlayerShopCommand extends Command { + + public static final String BASE_PERM = "playershops.command"; // TODO load from config + + // subcommand label -> subcommand + private static final Map SUBCOMMANDS = PlayerShopCommands.make(() -> { + final Map, Subcommand> commands = new HashMap<>(); + + commands.put(Set.of("reload"), new ReloadCommand()); + commands.put(Set.of("checkstock"), new CheckStockCommand()); + commands.put(Set.of("open"), new OpenCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + }); + // alias -> subcommand label + private static final Map ALIASES = PlayerShopCommands.make(() -> { + final Map> aliases = new HashMap<>(); + + aliases.put("reload", Set.of("reloadconfig")); + + return aliases.entrySet().stream() + .flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + }); + + public PlayerShopCommand() { + super("playershop"); + this.description = "PlayerShop related commands"; + this.usageMessage = "/playershop [" + String.join(" | ", SUBCOMMANDS.keySet()) + "]"; + final List permissions = new ArrayList<>(); + permissions.add(BASE_PERM); + permissions.addAll(SUBCOMMANDS.keySet().stream().map(s -> BASE_PERM + "." + s).toList()); + this.setPermission(String.join(";", permissions)); + final PluginManager pluginManager = Bukkit.getServer().getPluginManager(); + for (final String perm : permissions) { + pluginManager.addPermission(new Permission(perm, PermissionDefault.OP)); + } + } + + private static boolean testPermission(final CommandSender sender, final String permission) { + if (sender.hasPermission(BASE_PERM + "." + permission)) { + return true; + } + sender.sendMessage(Bukkit.permissionMessage()); + return false; + } + + @Override + public @NotNull List tabComplete( + final @NotNull CommandSender sender, + final @NotNull String alias, + final String[] args, + final @Nullable Location location + ) throws IllegalArgumentException { + if (args.length <= 1) { + return PlayerShopCommands.getListMatchingLast(sender, args, SUBCOMMANDS.keySet(), BASE_PERM); + } + + final @Nullable Pair subCommand = resolveCommand(args[0]); + if (subCommand != null) { + return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length)); + } + + return Collections.emptyList(); + } + + @Override + public boolean execute( + final @NotNull CommandSender sender, + final @NotNull String commandLabel, + final String[] args + ) { + if (!testPermission(sender)) { + return true; + } + + if (args.length == 0) { + sender.sendMessage(text("Usage: " + this.usageMessage, RED)); + return false; + } + final @Nullable Pair subCommand = resolveCommand(args[0]); + + if (subCommand == null) { + sender.sendMessage(text("Usage: " + this.usageMessage, RED)); + return false; + } + + if (!testPermission(sender, subCommand.first())) { + return true; + } + final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length); + return subCommand.second().execute(sender, subCommand.first(), choppedArgs); + } + + private static @Nullable Pair resolveCommand(String label) { + label = label.toLowerCase(Locale.ENGLISH); + @Nullable Subcommand subCommand = SUBCOMMANDS.get(label); + if (subCommand == null) { + final @Nullable String command = ALIASES.get(label); + if (command != null) { + label = command; + subCommand = SUBCOMMANDS.get(command); + } + } + + if (subCommand != null) { + return Pair.of(label, subCommand); + } + + return null; + } + + +} diff --git a/src/main/java/com/alttd/playershops/commands/PlayerShopCommands.java b/src/main/java/com/alttd/playershops/commands/PlayerShopCommands.java new file mode 100644 index 0000000..90e9569 --- /dev/null +++ b/src/main/java/com/alttd/playershops/commands/PlayerShopCommands.java @@ -0,0 +1,58 @@ +package com.alttd.playershops.commands; + +import com.google.common.base.Functions; +import com.google.common.collect.Lists; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import java.util.*; +import java.util.function.Supplier; + +public class PlayerShopCommands { + + private PlayerShopCommands() { + } + + private static final Map COMMANDS = new HashMap<>(); + static { + COMMANDS.put("playershop", new PlayerShopCommand()); + } + + public static void registerCommands() { + COMMANDS.forEach((s, command) -> { + Bukkit.getCommandMap().register(s, command.getName(), command); + }); + } + + // Code from Mojang - copyright them | Altered to fit our needs + public static List getListMatchingLast(final CommandSender sender, final String[] args, final String basePermission, final String... matches) { + return getListMatchingLast(sender, args, Arrays.asList(matches), basePermission); + } + + public static boolean matches(final String s, final String s1) { + return s1.regionMatches(true, 0, s, 0, s.length()); + } + + public static List getListMatchingLast(final CommandSender sender, final String[] strings, final Collection collection, final String basePermission) { + String last = strings[strings.length - 1]; + ArrayList results = Lists.newArrayList(); + + if (!collection.isEmpty()) { + + for (String s1 : collection.stream().map(Functions.toStringFunction()).toList()) { + if (matches(last, s1) && (sender.hasPermission(basePermission + "." + s1))) { + results.add(s1); + } + } + + } + + return results; + } + + public static T make(Supplier factory) { + return factory.get(); + } + // end copy stuff +} diff --git a/src/main/java/com/alttd/playershops/commands/ShopCommand.java b/src/main/java/com/alttd/playershops/commands/ShopCommand.java deleted file mode 100644 index 52ea77b..0000000 --- a/src/main/java/com/alttd/playershops/commands/ShopCommand.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.alttd.playershops.commands; - -import com.alttd.playershops.PlayerShops; -import com.alttd.playershops.gui.HomeGui; -import com.alttd.playershops.utils.Util; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabCompleter; -import org.bukkit.entity.Player; - -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -// expose brig in Galaxy? -public class ShopCommand implements CommandExecutor, TabCompleter { - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!(sender instanceof Player player)) return true; - if (args.length == 0) { - return true; - } - switch (args[0].toLowerCase()) { - case "reload": - PlayerShops.getInstance().reloadConfigs(); - break; - case "open": - HomeGui gui = new HomeGui(player.getUniqueId()); - gui.open(); - break; - case "checkstock": - if (!player.hasPermission("playershops.command.playershop.checkstock")) { - sender.sendMessage(Util.parseMiniMessage("playershops.command.playershop.checkstock'>You do not have permission for this command")); - break; - } - new CheckStockCommand(PlayerShops.getInstance(), player, args); - break; - default: - sender.sendMessage("invalid command useage"); - break; - } - return false; - } - - @Override - public List onTabComplete(CommandSender sender, Command cmd, String label, String[] args) { - if (args.length == 1) { - return Stream.of("reload", "open") - .filter(arg -> arg.startsWith(args[0].toLowerCase())) - .collect(Collectors.toList()); - } - return null; - } -} diff --git a/src/main/java/com/alttd/playershops/commands/Subcommand.java b/src/main/java/com/alttd/playershops/commands/Subcommand.java new file mode 100644 index 0000000..25261af --- /dev/null +++ b/src/main/java/com/alttd/playershops/commands/Subcommand.java @@ -0,0 +1,19 @@ +package com.alttd.playershops.commands; + +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +import java.util.Collections; +import java.util.List; + +@DefaultQualifier(NonNull.class) +public interface Subcommand { + + boolean execute(CommandSender sender, String subCommand, String[] args); + + default List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { + return Collections.emptyList(); + } + +} diff --git a/src/main/java/com/alttd/playershops/commands/CheckStockCommand.java b/src/main/java/com/alttd/playershops/commands/subcommands/CheckStockCommand.java similarity index 65% rename from src/main/java/com/alttd/playershops/commands/CheckStockCommand.java rename to src/main/java/com/alttd/playershops/commands/subcommands/CheckStockCommand.java index 4f2c20d..177b12c 100644 --- a/src/main/java/com/alttd/playershops/commands/CheckStockCommand.java +++ b/src/main/java/com/alttd/playershops/commands/subcommands/CheckStockCommand.java @@ -1,6 +1,9 @@ -package com.alttd.playershops.commands; +package com.alttd.playershops.commands.subcommands; import com.alttd.playershops.PlayerShops; +import com.alttd.playershops.commands.PlayerShopCommand; +import com.alttd.playershops.commands.PlayerShopCommands; +import com.alttd.playershops.commands.Subcommand; import com.alttd.playershops.shop.PlayerShop; import com.alttd.playershops.utils.ShopUtil; import com.alttd.playershops.utils.Util; @@ -8,34 +11,58 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import org.bukkit.Location; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; -public class CheckStockCommand { - private int playerX; - private int playerZ; - private int radius; +@DefaultQualifier(NonNull.class) +public class CheckStockCommand implements Subcommand { + + @Override + public boolean execute(CommandSender sender, String subCommand, String[] args) { + return this.doStockCheck(sender, args); + } + + @Override + public List tabComplete(final CommandSender sender, final String subCommand, final String[] args) { + // TODO give some default tab completions + if (args.length == 1) { + return PlayerShopCommands.getListMatchingLast(sender, args, PlayerShopCommand.BASE_PERM + ".checkstock", "help"); + } else if (args.length == 2) { +// return PlayerShopCommands.getListMatchingLast(sender, args, "radius"); + return Collections.emptyList(); + } + return Collections.emptyList(); + } + private int minimumStock = -1; - private final PlayerShops plugin; - public CheckStockCommand(PlayerShops plugin, Player player, String[] args) { - this.plugin = plugin; + + public boolean doStockCheck(final CommandSender sender, final String[] args) { + if (!(sender instanceof Player player)) { + sender.sendMessage("Only players can use this command."); + return false; + } if (args.length != 2 && args.length != 3) { player.sendMessage(Util.parseMiniMessage("Invalid command syntax, use /checkstock [minimum stock]")); - return; + return false; } + int radius; try { radius = Integer.parseInt(args[1]); } catch (NumberFormatException e) { player.sendMessage(Util.parseMiniMessage("radius has to be a valid number, use /checkstock [minimum stock]")); - return; + return false; } if (radius > 100 || radius <= 0) { player.sendMessage(Util.parseMiniMessage("Please keep the radius between 1 and 100")); - return; + return false; } if (args.length == 3) { @@ -43,17 +70,16 @@ public class CheckStockCommand { minimumStock = Integer.parseInt(args[2]); } catch (NumberFormatException e) { player.sendMessage(Util.parseMiniMessage("minium stock has to be a valid number, use /checkstock [minimum stock]")); - return; + return false; } } - playerX = player.getLocation().getBlockX(); - playerZ = player.getLocation().getBlockZ(); - List stockList = checkStock(); + List stockList = checkStock(player.getLocation().getBlockX(), player.getLocation().getBlockZ(), radius); sendStockMessage(player, stockList); + return true; } - private List checkStock() { - List shops = plugin.getShopHandler().getShopsInRadius(playerX, playerZ, radius); + private List checkStock(int x, int z, int radius) { + List shops = PlayerShops.getInstance().getShopHandler().getShopsInRadius(x, z, radius); if (minimumStock != -1) shops = shops.stream().filter(shop -> shop.getRemainingStock() < minimumStock).collect(Collectors.toList()); return shops.stream().map(shop -> { diff --git a/src/main/java/com/alttd/playershops/commands/subcommands/OpenCommand.java b/src/main/java/com/alttd/playershops/commands/subcommands/OpenCommand.java new file mode 100644 index 0000000..b3f3990 --- /dev/null +++ b/src/main/java/com/alttd/playershops/commands/subcommands/OpenCommand.java @@ -0,0 +1,24 @@ +package com.alttd.playershops.commands.subcommands; + +import com.alttd.playershops.commands.Subcommand; +import com.alttd.playershops.gui.HomeGui; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public class OpenCommand implements Subcommand { + + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { + if (!(sender instanceof Player player)) { + sender.sendMessage("Only players can use this command."); + return false; + } + HomeGui gui = new HomeGui(player.getUniqueId()); + gui.open(); + return true; + } + +} diff --git a/src/main/java/com/alttd/playershops/commands/subcommands/ReloadCommand.java b/src/main/java/com/alttd/playershops/commands/subcommands/ReloadCommand.java new file mode 100644 index 0000000..a918416 --- /dev/null +++ b/src/main/java/com/alttd/playershops/commands/subcommands/ReloadCommand.java @@ -0,0 +1,19 @@ +package com.alttd.playershops.commands.subcommands; + +import com.alttd.playershops.PlayerShops; +import com.alttd.playershops.commands.Subcommand; +import org.bukkit.command.CommandSender; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +public class ReloadCommand implements Subcommand { + + @Override + public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { + PlayerShops.getInstance().reloadConfigs(); + // Todo message when config is reloaded + return true; + } + +}