/****************************************************************************** * 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; } }