diff --git a/src/main/java/com/onarandombox/MultiverseCore/MultiverseCore.java b/src/main/java/com/onarandombox/MultiverseCore/MultiverseCore.java index df00fbe6..cb4857f4 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/MultiverseCore.java +++ b/src/main/java/com/onarandombox/MultiverseCore/MultiverseCore.java @@ -68,6 +68,7 @@ import com.onarandombox.MultiverseCore.commands.TeleportCommand; import com.onarandombox.MultiverseCore.commands.UnloadCommand; import com.onarandombox.MultiverseCore.commands.VersionCommand; import com.onarandombox.MultiverseCore.commands.WhoCommand; +import com.onarandombox.MultiverseCore.commandtools.queue.CommandQueueManager; import com.onarandombox.MultiverseCore.destination.AnchorDestination; import com.onarandombox.MultiverseCore.destination.BedDestination; import com.onarandombox.MultiverseCore.destination.CannonDestination; @@ -204,6 +205,7 @@ public class MultiverseCore extends JavaPlugin implements MVPlugin, Core { // Setup our Map for our Commands using the CommandHandler. private CommandHandler commandHandler; + private CommandQueueManager commandQueueManager; private static final String LOG_TAG = "[Multiverse-Core]"; @@ -288,6 +290,7 @@ public class MultiverseCore extends JavaPlugin implements MVPlugin, Core { // Setup the command manager this.commandHandler = new CommandHandler(this, this.ph); + this.commandQueueManager = new CommandQueueManager(this); // Call the Function to assign all the Commands to their Class. this.registerCommands(); @@ -917,6 +920,15 @@ public class MultiverseCore extends JavaPlugin implements MVPlugin, Core { return this.commandHandler; } + /** + * {@inheritDoc} + */ + @Override + @Deprecated + public CommandQueueManager getCommandQueueManager() { + return commandQueueManager; + } + /** * Gets the log-tag. * diff --git a/src/main/java/com/onarandombox/MultiverseCore/api/Core.java b/src/main/java/com/onarandombox/MultiverseCore/api/Core.java index ff8513d4..85ac5934 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/api/Core.java +++ b/src/main/java/com/onarandombox/MultiverseCore/api/Core.java @@ -8,6 +8,7 @@ package com.onarandombox.MultiverseCore.api; import buscript.Buscript; +import com.onarandombox.MultiverseCore.commandtools.queue.CommandQueueManager; import com.onarandombox.MultiverseCore.destination.DestinationFactory; import com.onarandombox.MultiverseCore.utils.AnchorManager; import com.onarandombox.MultiverseCore.utils.MVEconomist; @@ -86,6 +87,15 @@ public interface Core { */ CommandHandler getCommandHandler(); + /** + * Manager for command that requires /mv confirm before execution. + * + * @return A non-null {@link CommandQueueManager}. + * @deprecated To be moved to new command manager in 5.0.0 + */ + @Deprecated + CommandQueueManager getCommandQueueManager(); + /** * Gets the factory class responsible for loading many different destinations * on demand. diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/ConfirmCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/ConfirmCommand.java index 2a68f353..498a1431 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/ConfirmCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/ConfirmCommand.java @@ -33,7 +33,7 @@ public class ConfirmCommand extends MultiverseCommand { @Override public void runCommand(CommandSender sender, List args) { - this.plugin.getCommandHandler().confirmQueuedCommand(sender); + this.plugin.getCommandQueueManager().runQueuedCommand(sender); } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/DeleteCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/DeleteCommand.java index 2e7b3c8b..60345493 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/DeleteCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/DeleteCommand.java @@ -8,9 +8,11 @@ package com.onarandombox.MultiverseCore.commands; import com.onarandombox.MultiverseCore.MultiverseCore; +import com.onarandombox.MultiverseCore.commandtools.queue.QueuedCommand; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.permissions.PermissionDefault; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; @@ -35,11 +37,24 @@ public class DeleteCommand extends MultiverseCommand { public void runCommand(CommandSender sender, List args) { String worldName = args.get(0); - Class[] paramTypes = {String.class}; - List objectArgs = new ArrayList(args); - this.plugin.getCommandHandler() - .queueCommand(sender, "mvdelete", "deleteWorld", objectArgs, - paramTypes, ChatColor.GREEN + "World '" + worldName + "' Deleted!", - ChatColor.RED + "World '" + worldName + "' could NOT be deleted!"); + this.plugin.getCommandQueueManager().addToQueue(new QueuedCommand( + sender, + deleteRunnable(sender, worldName), + String.format("Are you sure you want to delete world '%s'? You cannot undo this action.", worldName) + )); + } + + private Runnable deleteRunnable(@NotNull CommandSender sender, + @NotNull String worldName) { + + return () -> { + sender.sendMessage(String.format("Deleting world '%s'...", worldName)); + if (this.plugin.getMVWorldManager().deleteWorld(worldName)) { + sender.sendMessage(String.format("%sWorld %s was deleted!", ChatColor.GREEN, worldName)); + return; + } + sender.sendMessage(String.format("%sThere was an issue deleting '%s'! Please check console for errors.", + ChatColor.RED, worldName)); + }; } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/RegenCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/RegenCommand.java index c93e36a7..a7bee95e 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/RegenCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/RegenCommand.java @@ -8,9 +8,11 @@ package com.onarandombox.MultiverseCore.commands; import com.onarandombox.MultiverseCore.MultiverseCore; +import com.onarandombox.MultiverseCore.commandtools.queue.QueuedCommand; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.permissions.PermissionDefault; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; @@ -37,17 +39,30 @@ public class RegenCommand extends MultiverseCommand { @Override public void runCommand(CommandSender sender, List args) { - Boolean useseed = (!(args.size() == 1)); - Boolean randomseed = (args.size() == 2 && args.get(1).equalsIgnoreCase("-s")); + String worldName = args.get(0); + boolean useseed = (!(args.size() == 1)); + boolean randomseed = (args.size() == 2 && args.get(1).equalsIgnoreCase("-s")); String seed = (args.size() == 3) ? args.get(2) : ""; - Class[] paramTypes = {String.class, Boolean.class, Boolean.class, String.class}; - List objectArgs = new ArrayList(); - objectArgs.add(args.get(0)); - objectArgs.add(useseed); - objectArgs.add(randomseed); - objectArgs.add(seed); - this.plugin.getCommandHandler().queueCommand(sender, "mvregen", "regenWorld", objectArgs, - paramTypes, ChatColor.GREEN + "World Regenerated!", ChatColor.RED + "World could NOT be regenerated!"); + this.plugin.getCommandQueueManager().addToQueue(new QueuedCommand( + sender, + doWorldRegen(sender, worldName, useseed, randomseed, seed), + String.format("Are you sure you want to regen '%s'? You cannot undo this action.", worldName) + )); + } + + private Runnable doWorldRegen(@NotNull CommandSender sender, + @NotNull String worldName, + boolean useSeed, + boolean randomSeed, + @NotNull String seed) { + + return () -> { + if (this.plugin.getMVWorldManager().regenWorld(worldName, useSeed, randomSeed, seed)) { + sender.sendMessage(ChatColor.GREEN + "World Regenerated!"); + return; + } + sender.sendMessage(ChatColor.RED + "World could NOT be regenerated!"); + }; } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/TeleportCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/TeleportCommand.java index 17ad7714..8a8b6712 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/TeleportCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/TeleportCommand.java @@ -11,6 +11,7 @@ import com.dumptruckman.minecraft.util.Logging; import com.onarandombox.MultiverseCore.MultiverseCore; import com.onarandombox.MultiverseCore.api.Teleporter; import com.onarandombox.MultiverseCore.api.MVDestination; +import com.onarandombox.MultiverseCore.commandtools.queue.QueuedCommand; import com.onarandombox.MultiverseCore.destination.CustomTeleporterDestination; import com.onarandombox.MultiverseCore.destination.DestinationFactory; import com.onarandombox.MultiverseCore.destination.InvalidDestination; @@ -165,24 +166,27 @@ public class TeleportCommand extends MultiverseCommand { if (result == TeleportResult.FAIL_UNSAFE) { Logging.fine("Could not teleport " + teleportee.getName() + " to " + plugin.getLocationManipulation().strCoordsRaw(d.getLocation(teleportee))); - Logging.fine("Queueing Command"); - Class[] paramTypes = { CommandSender.class, Player.class, Location.class }; - List items = new ArrayList(); - items.add(teleporter); - items.add(teleportee); - items.add(d.getLocation(teleportee)); + String player = "you"; if (!teleportee.equals(teleporter)) { player = teleportee.getName(); } - String message = String.format("%sMultiverse %sdid not teleport %s%s %sto %s%s %sbecause it was unsafe.", - ChatColor.GREEN, ChatColor.WHITE, ChatColor.AQUA, player, ChatColor.WHITE, ChatColor.DARK_AQUA, d.getName(), ChatColor.WHITE); - this.plugin.getCommandHandler().queueCommand(sender, "mvteleport", "teleportPlayer", items, - paramTypes, message, "Would you like to try anyway?", "", "", UNSAFE_TELEPORT_EXPIRE_DELAY); + + this.plugin.getCommandQueueManager().addToQueue(new QueuedCommand( + sender, + doUnsafeTeleport(teleporter, teleportee, d.getLocation(teleportee)), + String.format("%sMultiverse %sdid not teleport %s%s %sto %s%s %sbecause it was unsafe. Would you like to try anyway?", + ChatColor.GREEN, ChatColor.WHITE, ChatColor.AQUA, player, ChatColor.WHITE, ChatColor.DARK_AQUA, d.getName(), ChatColor.WHITE), + UNSAFE_TELEPORT_EXPIRE_DELAY + )); } // else: Player was teleported successfully (or the tp event was fired I should say) } + private Runnable doUnsafeTeleport(CommandSender teleporter, Player player, Location location) { + return () -> this.plugin.getSafeTTeleporter().safelyTeleport(teleporter, player, location, false); + } + private boolean checkSendPermissions(CommandSender teleporter, Player teleportee, MVDestination destination) { if (teleporter.equals(teleportee)) { if (!this.plugin.getMVPerms().hasPermission(teleporter, "multiverse.teleport.self." + destination.getIdentifier(), true)) { diff --git a/src/main/java/com/onarandombox/MultiverseCore/commandtools/queue/CommandQueueManager.java b/src/main/java/com/onarandombox/MultiverseCore/commandtools/queue/CommandQueueManager.java new file mode 100644 index 00000000..a5c808e0 --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/commandtools/queue/CommandQueueManager.java @@ -0,0 +1,158 @@ +/****************************************************************************** + * Multiverse 2 Copyright (c) the Multiverse Team 2020. * + * Multiverse 2 is licensed under the BSD License. * + * For more information please check the README.md file included * + * with this project. * + ******************************************************************************/ + +package com.onarandombox.MultiverseCore.commandtools.queue; + +import com.dumptruckman.minecraft.util.Logging; +import com.onarandombox.MultiverseCore.MultiverseCore; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.block.data.type.CommandBlock; +import org.bukkit.command.BlockCommandSender; +import org.bukkit.command.CommandSender; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Managers the queuing of dangerous commands that needs to use '/mv confirm' before executing. + */ +public class CommandQueueManager { + + private static final long TICKS_PER_SECOND = 20; + private static final DummyCommandBlockSender COMMAND_BLOCK = new DummyCommandBlockSender(); + + private final MultiverseCore plugin; + private final Map queuedCommandMap; + + public CommandQueueManager(@NotNull MultiverseCore plugin) { + this.plugin = plugin; + this.queuedCommandMap = new WeakHashMap<>(); + } + + /** + * Adds a queue command into queue. + * + * @param queuedCommand The queue command to add. + */ + public void addToQueue(QueuedCommand queuedCommand) { + CommandSender targetSender = parseSender(queuedCommand.getSender()); + + // Since only one command is stored in queue per sender, we remove the old one. + this.removeFromQueue(targetSender); + + Logging.finer("Add new command to queue for sender %s.", targetSender); + this.queuedCommandMap.put(targetSender, queuedCommand); + queuedCommand.setExpireTask(runExpireLater(queuedCommand)); + + queuedCommand.getSender().sendMessage(queuedCommand.getPrompt()); + queuedCommand.getSender().sendMessage(String.format("Run %s/mv confirm %sto continue. This will expire in %s seconds.", + ChatColor.GREEN, ChatColor.WHITE, queuedCommand.getValidDuration())); + } + + /** + * Expire task that remove {@link QueuedCommand} from queue after valid duration defined. + * + * @param queuedCommand Command to run the expire task on. + * @return The expire {@link BukkitTask}. + */ + @NotNull + private BukkitTask runExpireLater(@NotNull QueuedCommand queuedCommand) { + return Bukkit.getScheduler().runTaskLater( + this.plugin, + expireRunnable(queuedCommand), + queuedCommand.getValidDuration() * TICKS_PER_SECOND + ); + } + + /** + * Runnable responsible for expiring the queued command. + * + * @param queuedCommand Command to create the expire task on. + * @return The expire runnable. + */ + @NotNull + private Runnable expireRunnable(@NotNull QueuedCommand queuedCommand) { + return () -> { + CommandSender targetSender = parseSender(queuedCommand.getSender()); + QueuedCommand matchingQueuedCommand = this.queuedCommandMap.get(targetSender); + if (!queuedCommand.equals(matchingQueuedCommand) || queuedCommand.getExpireTask().isCancelled()) { + // To be safe, but this shouldn't happen since we cancel old commands before add new once. + Logging.finer("This is an old queue command already."); + return; + } + queuedCommand.getSender().sendMessage("Your queued command has expired."); + this.queuedCommandMap.remove(queuedCommand.getSender()); + }; + } + + /** + * Runs the command in queue for the given sender, if any. + * + * @param sender {@link CommandSender} that confirmed the command. + * @return True if queued command ran successfully, else false. + */ + public boolean runQueuedCommand(@NotNull CommandSender sender) { + CommandSender targetSender = parseSender(sender); + QueuedCommand queuedCommand = this.queuedCommandMap.get(targetSender); + if (queuedCommand == null) { + sender.sendMessage(ChatColor.RED + "You do not have any commands in queue."); + return false; + } + Logging.finer("Running queued command..."); + queuedCommand.getAction().run(); + return removeFromQueue(targetSender); + } + + /** + * Since only one command is stored in queue per sender, we remove the old one. + * + * @param sender The {@link CommandSender} that executed the command. + * @return True if queue command is removed from sender successfully, else false. + */ + public boolean removeFromQueue(@NotNull CommandSender sender) { + CommandSender targetSender = parseSender(sender); + QueuedCommand previousCommand = this.queuedCommandMap.remove(targetSender); + if (previousCommand == null) { + Logging.finer("No queue command to remove for sender %s.", targetSender.getName()); + return false; + } + previousCommand.getExpireTask().cancel(); + Logging.finer("Removed queue command for sender %s.", targetSender.getName()); + return true; + } + + /** + * To allow all CommandBlocks to be a common sender with use of {@link DummyCommandBlockSender}. + * So confirm command can be used for a queue command on another command block. + * + * @param sender The sender to parse. + * @return The sender, or if its a command block, a {@link DummyCommandBlockSender}. + */ + @NotNull + private CommandSender parseSender(@NotNull CommandSender sender) { + Logging.fine(sender.getClass().getName()); + if (isCommandBlock(sender)) { + Logging.finer("Is command block."); + return COMMAND_BLOCK; + } + return sender; + } + + /** + * Check if sender is a command block. + * + * @param sender The sender to check on. + * @return True if sender is a command block, else false. + */ + private boolean isCommandBlock(@NotNull CommandSender sender) { + return sender instanceof BlockCommandSender + && ((BlockCommandSender) sender).getBlock().getBlockData() instanceof CommandBlock; + } +} diff --git a/src/main/java/com/onarandombox/MultiverseCore/commandtools/queue/DummyCommandBlockSender.java b/src/main/java/com/onarandombox/MultiverseCore/commandtools/queue/DummyCommandBlockSender.java new file mode 100644 index 00000000..819602b6 --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/commandtools/queue/DummyCommandBlockSender.java @@ -0,0 +1,104 @@ +package com.onarandombox.MultiverseCore.commandtools.queue; + +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.command.CommandSender; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +/** + * Used by {@link CommandQueueManager}, so different commands block can be recognised as one. + */ +class DummyCommandBlockSender implements CommandSender { + + @Override + public void sendMessage(@NotNull String message) { + throw new UnsupportedOperationException(); + } + + @Override + public void sendMessage(@NotNull String[] messages) { + throw new UnsupportedOperationException(); + } + + @Override + public @NotNull Server getServer() { + return Bukkit.getServer(); + } + + @Override + public @NotNull String getName() { + return "DummyCommandBlockSender"; + } + + @Override + public boolean isPermissionSet(@NotNull String name) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isPermissionSet(@NotNull Permission perm) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasPermission(@NotNull String name) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasPermission(@NotNull Permission perm) { + throw new UnsupportedOperationException(); + } + + @Override + public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value) { + throw new UnsupportedOperationException(); + } + + @Override + public @NotNull PermissionAttachment addAttachment(@NotNull Plugin plugin) { + throw new UnsupportedOperationException(); + } + + @Override + public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, @NotNull String name, boolean value, int ticks) { + throw new UnsupportedOperationException(); + } + + @Override + public @Nullable PermissionAttachment addAttachment(@NotNull Plugin plugin, int ticks) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeAttachment(@NotNull PermissionAttachment attachment) { + throw new UnsupportedOperationException(); + } + + @Override + public void recalculatePermissions() { + throw new UnsupportedOperationException(); + } + + @Override + public @NotNull Set getEffectivePermissions() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isOp() { + return true; + } + + @Override + public void setOp(boolean value) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/onarandombox/MultiverseCore/commandtools/queue/QueuedCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commandtools/queue/QueuedCommand.java new file mode 100644 index 00000000..5dedc99f --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/commandtools/queue/QueuedCommand.java @@ -0,0 +1,86 @@ +/****************************************************************************** + * Multiverse 2 Copyright (c) the Multiverse Team 2020. * + * Multiverse 2 is licensed under the BSD License. * + * For more information please check the README.md file included * + * with this project. * + ******************************************************************************/ + +package com.onarandombox.MultiverseCore.commandtools.queue; + +import org.bukkit.command.CommandSender; +import org.bukkit.scheduler.BukkitTask; +import org.jetbrains.annotations.NotNull; + +/** + * Represents a single command used in {@link CommandQueueManager} for confirming before running potentially + * dangerous action. + */ +public class QueuedCommand { + + private static final String DEFAULT_PROMPT_MESSAGE = "The command you are trying to run is deemed dangerous."; + private static final int DEFAULT_VALID_TIME = 10; + + private final CommandSender sender; + private final Runnable action; + private final String prompt; + private final int validDuration; + private BukkitTask expireTask; + + public QueuedCommand(CommandSender sender, Runnable action) { + this(sender, action, DEFAULT_PROMPT_MESSAGE, DEFAULT_VALID_TIME); + } + + public QueuedCommand(CommandSender sender, Runnable action, String prompt) { + this(sender, action, prompt, DEFAULT_VALID_TIME); + } + + public QueuedCommand(CommandSender sender, Runnable action, int validDuration) { + this(sender, action, DEFAULT_PROMPT_MESSAGE, validDuration); + } + + /** + * Creates a new queue command, to be registered at {@link CommandQueueManager#addToQueue(QueuedCommand)}. + * + * @param sender The sender that ran the command needed for confirmation. + * @param action The logic to execute upon confirming. + * @param prompt Question to ask sender to confirm. + * @param validDuration Duration in which the command is valid for confirm in seconds. + */ + public QueuedCommand(CommandSender sender, Runnable action, String prompt, int validDuration) { + this.sender = sender; + this.action = action; + this.prompt = prompt; + this.validDuration = validDuration; + } + + @NotNull + CommandSender getSender() { + return sender; + } + + @NotNull + String getPrompt() { + return prompt; + } + + int getValidDuration() { + return validDuration; + } + + @NotNull + Runnable getAction() { + return action; + } + + @NotNull + BukkitTask getExpireTask() { + return expireTask; + } + + void setExpireTask(@NotNull BukkitTask expireTask) { + if (this.expireTask != null) { + throw new IllegalStateException("This queue command already has an expire task. You can't register twice!"); + } + this.expireTask = expireTask; + } +} diff --git a/src/main/java/com/onarandombox/MultiverseCore/commandtools/queue/package-info.java b/src/main/java/com/onarandombox/MultiverseCore/commandtools/queue/package-info.java new file mode 100644 index 00000000..075ff0df --- /dev/null +++ b/src/main/java/com/onarandombox/MultiverseCore/commandtools/queue/package-info.java @@ -0,0 +1,4 @@ +/** + * Manager queuing of dangerous commands in need of confirmation. + */ +package com.onarandombox.MultiverseCore.commandtools.queue; \ No newline at end of file