diff --git a/Datura/src/main/java/me/totalfreedom/datura/cmd/CageCommand.java b/Datura/src/main/java/me/totalfreedom/datura/cmd/CageCommand.java index 38b2050..91c12fc 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/cmd/CageCommand.java +++ b/Datura/src/main/java/me/totalfreedom/datura/cmd/CageCommand.java @@ -48,20 +48,16 @@ public class CageCommand extends Commander @Subcommand(permission = "datura.cage", args = {Player.class, String.class}) public void cagePlayer(final CommandSender sender, final Player player, final String string) { - switch (string.toLowerCase()) + if (string.equalsIgnoreCase("on")) { - case "on" -> - { - ((Datura) getPlugin()).getCager() - .cagePlayer(player.getUniqueId()); - sender.sendPlainMessage("Caged " + player.getName() + "."); - } - case "off" -> - { - ((Datura) getPlugin()).getCager() - .uncagePlayer(player.getUniqueId()); - sender.sendPlainMessage("Liberated " + player.getName() + "."); - } + ((Datura) getPlugin()).getCager() + .cagePlayer(player.getUniqueId()); + sender.sendPlainMessage("Caged " + player.getName() + "."); + } else if (string.equalsIgnoreCase("off")) + { + ((Datura) getPlugin()).getCager() + .uncagePlayer(player.getUniqueId()); + sender.sendPlainMessage("Liberated " + player.getName() + "."); } } @@ -70,20 +66,16 @@ public class CageCommand extends Commander public void cagePlayer(final CommandSender sender, final Player player, final String string, final Material material) { - switch (string.toLowerCase()) + if (string.equalsIgnoreCase("on")) { - case "on" -> - { - ((Datura) getPlugin()).getCager() - .cagePlayer(player.getUniqueId(), material); - sender.sendPlainMessage("Caged " + player.getName() + "."); - } - case "off" -> - { - ((Datura) getPlugin()).getCager() - .uncagePlayer(player.getUniqueId()); - sender.sendPlainMessage("Liberated " + player.getName() + "."); - } + ((Datura) getPlugin()).getCager() + .cagePlayer(player.getUniqueId(), material); + sender.sendPlainMessage("Caged " + player.getName() + "."); + } else if (string.equalsIgnoreCase("off")) + { + ((Datura) getPlugin()).getCager() + .uncagePlayer(player.getUniqueId()); + sender.sendPlainMessage("Liberated " + player.getName() + "."); } } } diff --git a/Datura/src/main/java/me/totalfreedom/datura/cmd/HaltCommand.java b/Datura/src/main/java/me/totalfreedom/datura/cmd/HaltCommand.java new file mode 100644 index 0000000..0edca7c --- /dev/null +++ b/Datura/src/main/java/me/totalfreedom/datura/cmd/HaltCommand.java @@ -0,0 +1,90 @@ +package me.totalfreedom.datura.cmd; + +import me.totalfreedom.base.Shortcuts; +import me.totalfreedom.command.Commander; +import me.totalfreedom.command.annotation.Info; +import me.totalfreedom.command.annotation.Permissive; +import me.totalfreedom.command.annotation.Subcommand; +import me.totalfreedom.datura.Datura; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; + +@Info(name = "halt", description = "Halt a single player, or every player.", usage = "/ ") +@Permissive(perm = "datura.halt") +public class HaltCommand extends Commander +{ + private final Datura plugin = Shortcuts.provideModule(Datura.class) + .getModule(); + + /** + * Initializes this command object. The provided {@link JavaPlugin} should be the plugin which contains the + * command. + *

+ * This constructor will automatically register all subcommands and completions for this command. It will also + * automatically infer all required information from the provided {@link Info} and {@link Permissive} annotations. + * + * @param plugin The plugin which contains this command. + */ + protected HaltCommand(@NotNull final JavaPlugin plugin) + { + super(plugin); + } + + + @Subcommand(permission = "datura.halt", args = {Player.class, String.class}) + public void haltPlayer(final CommandSender sender, final Player target, final String toggle) + { + if (toggle.equalsIgnoreCase("on")) + { + plugin.getHalter() + .halt(target.getUniqueId()); + + target.sendPlainMessage("You have been frozen!"); + sender.sendPlainMessage("You have halted " + target.getName() + "."); + } else if (toggle.equalsIgnoreCase("off")) + { + plugin.getHalter() + .stop(target.getUniqueId()); + + target.sendPlainMessage("You have been unfrozen!"); + sender.sendPlainMessage("You have unhalted " + target.getName() + "."); + } + } + + @Subcommand(permission = "datura.halt.all", args = {String.class, String.class}) + public void haltAll(final CommandSender sender, final String all, final String toggle) + { + if (all.equalsIgnoreCase("all")) + { + if (toggle.equalsIgnoreCase("on")) + { + Bukkit.getServer() + .getOnlinePlayers() + .forEach(player -> + { + plugin.getHalter() + .halt(player.getUniqueId()); + }); + + final Component message = sender.name() + .append(Component.text(": Freezing all players")) + .color(NamedTextColor.AQUA); + + Bukkit.broadcast(message); + sender.sendPlainMessage("All players have been halted."); + } else if (toggle.equalsIgnoreCase("off")) + { + plugin.getHalter() + .clear(); + + Bukkit.broadcast(Component.text("All players have been unfrozen!", NamedTextColor.AQUA)); + sender.sendPlainMessage("All players have been unhalted."); + } + } + } +} diff --git a/Datura/src/main/java/me/totalfreedom/datura/punishment/Halter.java b/Datura/src/main/java/me/totalfreedom/datura/punishment/Halter.java index 11ef9be..af61b9b 100644 --- a/Datura/src/main/java/me/totalfreedom/datura/punishment/Halter.java +++ b/Datura/src/main/java/me/totalfreedom/datura/punishment/Halter.java @@ -22,6 +22,15 @@ public class Halter implements Listener this.haltedPlayers.add(uuid); } + public void stop(final UUID uuid) + { + this.haltedPlayers.remove(uuid); + } + + public void clear() { + this.haltedPlayers.clear(); + } + @EventHandler public void playerMove(final PlayerMoveEvent event) { diff --git a/Patchwork/src/main/java/me/totalfreedom/base/Shortcuts.java b/Patchwork/src/main/java/me/totalfreedom/base/Shortcuts.java new file mode 100644 index 0000000..2194026 --- /dev/null +++ b/Patchwork/src/main/java/me/totalfreedom/base/Shortcuts.java @@ -0,0 +1,15 @@ +package me.totalfreedom.base; + +import me.totalfreedom.provider.ModuleProvider; +import org.bukkit.plugin.java.JavaPlugin; + +public final class Shortcuts +{ + private Shortcuts() { + throw new AssertionError(); + } + + public static ModuleProvider provideModule(final Class pluginClass) { + return CommonsBase.getInstance().getRegistrations().getModuleRegistry().getProvider(pluginClass); + } +} diff --git a/Patchwork/src/main/java/me/totalfreedom/command/BukkitDelegate.java b/Patchwork/src/main/java/me/totalfreedom/command/BukkitDelegate.java index 0a740d6..c122527 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/BukkitDelegate.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/BukkitDelegate.java @@ -1,12 +1,12 @@ package me.totalfreedom.command; -import me.totalfreedom.api.Context; import me.totalfreedom.command.annotation.Completion; import me.totalfreedom.command.annotation.Subcommand; import me.totalfreedom.provider.ContextProvider; import me.totalfreedom.utils.logging.FreedomLogger; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; @@ -120,7 +120,7 @@ public final class BukkitDelegate extends Command implements PluginIdentifiableC final Subcommand node) { final Class[] argTypes = node.args(); - if (argTypes.length != args.length) + if (argTypes.length > args.length) return; final Object[] objects = new Object[argTypes.length + 1]; @@ -130,11 +130,27 @@ public final class BukkitDelegate extends Command implements PluginIdentifiableC final Class argType = argTypes[i]; final String arg = args[i]; - if (argType == String.class) - continue; + if (argType.equals(String.class)) + { + if (i == argTypes.length - 1) + { + final String[] reasonArgs = Arrays.copyOfRange(args, i, args.length - 1); + final String reason = String.join(" ", reasonArgs); + objects[i] = reason; + } else + { + continue; + } + } - final Context context = () -> provider.fromString(arg, argType); - objects[i] = context.get(); + if (argType.equals(Location.class)) { + final String[] locationArgs = Arrays.copyOfRange(args, i, i + 3); + final String location = String.join(" ", locationArgs); + objects[i] = location; + } + + final Object obj = provider.fromString(arg, argType); + objects[i] = obj; } try { @@ -183,7 +199,7 @@ public final class BukkitDelegate extends Command implements PluginIdentifiableC "7", "8", "9")); - case "%location%" -> results.add("world,x,y,z"); + case "%location%" -> results.add("world x y z"); default -> results.add(p); } } diff --git a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Subcommand.java b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Subcommand.java index b203761..679fdb1 100644 --- a/Patchwork/src/main/java/me/totalfreedom/command/annotation/Subcommand.java +++ b/Patchwork/src/main/java/me/totalfreedom/command/annotation/Subcommand.java @@ -10,19 +10,26 @@ import java.lang.annotation.Target; /** * This annotation should be used to mark methods as subcommand methods. Subcommand methods can have custom arguments - * (current supported arguments can be found in the {@link ContextProvider}), and can also have a custom permission. - * These subcommands can also be annotated with {@link Completions} to provide tab completions for the subcommand. The - * subcommand method must be public, and must be in a class that is registered with the {@link CommandHandler}. - *
+ * (current supported arguments can be found in the {@link ContextProvider}), and can also have a custom + * permission. These subcommands can also be annotated with {@link Completions} to provide tab completions for the + * subcommand. The subcommand method must be public, and must be in a class that is registered with the + * {@link CommandHandler}. + *

* Tab completions with the {@link Completions} annotation are only supported for subcommands. When registering * completions, you only need to define the completion arguments a single time. If there are other methods which * function as optional additional arguments for the subcommand, the previously registered arguments will still be * present when the user does their tab completion. - *
+ *

* For example, if you have a subcommand method with the arguments {@code (Player, String)}, and another method which * has the arguments {@code (Player, String, String)}, the tab completions for the second method will still have the * {@code Player} and {@code String} arguments registered from the first method. You will only need to provide a * {@link Completion} for the additional 3rd argument. + *

+ * Additionally, if the final argument is a String object, the BukkitDelegate will automatically append any additional + * arguments to the end of the String. For example, if you have a subcommand method with the arguments + * {@code (Player, String)}, and the user executes the command with the arguments {@code /command playerName arg2 arg3}, + * the {@code String} argument will be {@code "arg2 arg3"}. This allows for us to use a String at the end of our + * subcommand arguments to allow for the user to input a reason. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) diff --git a/Patchwork/src/main/java/me/totalfreedom/provider/ContextProvider.java b/Patchwork/src/main/java/me/totalfreedom/provider/ContextProvider.java index 680219d..0b28a8b 100644 --- a/Patchwork/src/main/java/me/totalfreedom/provider/ContextProvider.java +++ b/Patchwork/src/main/java/me/totalfreedom/provider/ContextProvider.java @@ -1,5 +1,8 @@ package me.totalfreedom.provider; +import me.totalfreedom.api.Context; +import me.totalfreedom.command.BukkitDelegate; +import me.totalfreedom.command.annotation.Subcommand; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -13,6 +16,26 @@ import org.jetbrains.annotations.Nullable; import java.util.Objects; import java.util.stream.Stream; +/** + * This class is used to provide context to subcommand methods. This class is used by the BukkitDelegate to parse + * arguments for subcommands. The following types are supported: + *

    + *
  • Boolean
  • + *
  • Double
  • + *
  • Integer
  • + *
  • Long
  • + *
  • Float
  • + *
  • Material
  • + *
  • Player
  • + *
  • World
  • + *
  • Location
  • + *
  • CommandSender
  • + *
  • Component
  • + *
+ * All of these types can be parsed from a String input. If the String cannot be parsed into any of + * these types, then null will be returned. + * @see #fromString(String, Class) + */ public class ContextProvider { public T fromString(final String string, final Class clazz) @@ -104,25 +127,29 @@ public class ContextProvider } /** - * When using this method, the input string must be formatted as - *
- * worldName,x,y,z - *
+ * When using this method, the next four arguments must be world, x, y, z. + * The world must be a valid world name, and x, y, and z must be valid doubles. + * If any of these are invalid, this will return null. * * @param string The string to parse * @return A location object if xyz is valid + * @see BukkitDelegate#processSubCommands(String[], CommandSender, ContextProvider, Subcommand) */ private @Nullable Location toLocation(final String string) { - final String[] split = string.split(","); + final String[] split = string.split(" "); if (split.length != 4 || toWorld(split[0]) == null) return null; - final double x = Double.parseDouble(split[1]); - final double y = Double.parseDouble(split[2]); - final double z = Double.parseDouble(split[3]); + try { + final double x = Double.parseDouble(split[1]); + final double y = Double.parseDouble(split[2]); + final double z = Double.parseDouble(split[3]); - return new Location(toWorld(split[0]), x, y, z); + return new Location(toWorld(split[0]), x, y, z); + } catch (NumberFormatException ex) { + return null; + } } private @Nullable CommandSender toCommandSender(final String string)