diff --git a/locales/en-US.yml b/locales/en-US.yml index ef995917f..502667c5f 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -17,6 +17,9 @@ banner: general: success: "&aSuccess!" + request-cancelled: "&cConfirmation timeout - &brequest cancelled" + previous-request-cancelled: "&6Previous confirmation request cancelled" + confirm: "&cType command again within &b[seconds]s&c to confirm" errors: command-cancelled: "&cCommand cancelled" no-permission: "&cYou don't have permission to execute this command." @@ -121,6 +124,16 @@ commands: description: "set a player's rank on their island" unknown-rank: "&cUnknown rank!" rank-set: "&aRank set from [from] to [to]." + schem: + parameters: "" + description: "manipulate schems" + copy-first: "&cCopy a schem first!" + no-such-file: "&cNo such file!" + could-not-load: "&cCould not load that file!" + set-pos1: "&aPosition 1 set at [vector]" + set-pos2: "&aPosition 2 set at [vector]" + need-pos1-pos2: "&cSet pos1 and pos2 first!" + copied-blocks: "&bCopied [number] blocks to clipboard" island: about: description: "display copyright and license info" @@ -147,8 +160,6 @@ commands: must-remove-members: "You must remove all members from your island before you can restart it (/island kick )." none-left: "&cYou have no more resets left!" resets-left: "&cYou have [number] resets left" - confirm: "&cType &b/[label] reset confirm&c within [number]s to confirm reset" - cancelled: "&bReset cancelled" sethome: description: "set your teleport point for /island" must-be-on-your-island: "You must be on your island to set home!" diff --git a/src/main/java/us/tastybento/bskyblock/api/commands/CompositeCommand.java b/src/main/java/us/tastybento/bskyblock/api/commands/CompositeCommand.java index 29e342baa..e1ee5f991 100644 --- a/src/main/java/us/tastybento/bskyblock/api/commands/CompositeCommand.java +++ b/src/main/java/us/tastybento/bskyblock/api/commands/CompositeCommand.java @@ -2,6 +2,7 @@ package us.tastybento.bskyblock.api.commands; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; @@ -11,11 +12,13 @@ import java.util.Set; import java.util.UUID; import java.util.logging.Logger; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.command.PluginIdentifiableCommand; import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; import us.tastybento.bskyblock.BSkyBlock; import us.tastybento.bskyblock.Settings; @@ -35,7 +38,7 @@ import us.tastybento.bskyblock.util.Util; */ public abstract class CompositeCommand extends Command implements PluginIdentifiableCommand, BSBCommand { - private BSkyBlock plugin; + private final BSkyBlock plugin; /** * True if the command is for the player only (not for the console) @@ -70,28 +73,30 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi * The command chain from the very top, e.g., island team promote */ private String usage; - + /** * The prefix to be used in this command */ private String permissionPrefix = ""; - + /** * The world that this command operates in. This is an overworld and will cover any associated nether or end * If the world value does not exist, then the command is general across worlds */ private World world; - + /** * The addon creating this command, if any */ private Addon addon; - + /** * The top level label */ private String topLabel = ""; + private static Map toBeConfirmed = new HashMap<>(); + /** * Used only for testing.... */ @@ -136,7 +141,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi new DefaultHelpCommand(this); } } - + /** * This is the top-level command constructor for commands that have no parent. * @param label - string for this command @@ -225,14 +230,13 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi if (event.isCancelled()) { return true; } - // Execute and trim args return cmd.execute(user, Arrays.asList(args).subList(cmd.subCommandLevel, args.length)); } /** * Get the current composite command based on the arguments - * @param args + * @param args - arguments * @return the current composite command based on the arguments */ private CompositeCommand getCommandFromArgs(String[] args) { @@ -240,7 +244,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi // Run through any arguments for (String arg : args) { // get the subcommand corresponding to the arg - if (subCommand.hasSubCommmands()) { + if (subCommand.hasSubCommands()) { Optional sub = subCommand.getSubCommand(arg); if (!sub.isPresent()) { return subCommand; @@ -375,7 +379,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi /** * Check if this command has a specific sub command - * @param subCommand + * @param subCommand - sub command * @return true if this command has this sub command */ protected boolean hasSubCommand(String subCommand) { @@ -386,7 +390,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi * Check if this command has any sub commands * @return true if this command has subcommands */ - protected boolean hasSubCommmands() { + protected boolean hasSubCommands() { return !subCommands.isEmpty(); } @@ -419,7 +423,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi /** * Set whether this command is only for players - * @param onlyPlayer + * @param onlyPlayer - true if command only for players */ public void setOnlyPlayer(boolean onlyPlayer) { this.onlyPlayer = onlyPlayer; @@ -427,7 +431,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi /** * Sets the command parameters to be shown in help - * @param parameters + * @param parameters - string of parameters */ public void setParameters(String parameters) { this.parameters = parameters; @@ -440,7 +444,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi public void setPermission(String permission) { this.permission = permissionPrefix + permission; } - + /** * Inherits the permission from parent command */ @@ -480,7 +484,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi // Add any tab completion from the subcommand options.addAll(cmd.tabComplete(User.getInstance(sender), alias, new LinkedList<>(Arrays.asList(args))).orElse(new ArrayList<>())); // Add any sub-commands automatically - if (cmd.hasSubCommmands()) { + if (cmd.hasSubCommands()) { // Check if subcommands are visible to this sender for (CompositeCommand subCommand: cmd.getSubCommands().values()) { if (sender instanceof Player) { @@ -506,7 +510,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi /** * Show help - * @param command + * @param command - command that this help is for * @param user - the User * @return result of help command or false if no help defined */ @@ -562,11 +566,85 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi public Addon getAddon() { return addon; } - + /** * @return top level label, e.g., island */ public String getTopLabel() { return topLabel; } + + /** + * Tells user to confirm command by retyping + * @param user - user + * @param confirmed - runnable to be executed if confirmed + */ + public void askConfirmation(User user, Runnable confirmed) { + // Check for pending confirmations + if (toBeConfirmed.containsKey(user)) { + if (toBeConfirmed.get(user).getTopLabel().equals(getTopLabel()) && toBeConfirmed.get(user).getLabel().equalsIgnoreCase(getLabel())) { + toBeConfirmed.get(user).getTask().cancel(); + Bukkit.getScheduler().runTask(getPlugin(), toBeConfirmed.get(user).getRunnable()); + toBeConfirmed.remove(user); + return; + } else { + // Player has another outstanding confirmation request that will now be cancelled + user.sendMessage("general.previous-request-cancelled"); + } + } + // Tell user that they need to confirm + user.sendMessage("general.confirm", "[seconds]", String.valueOf(getSettings().getConfirmationTime())); + // Set up a cancellation task + BukkitTask task = Bukkit.getScheduler().runTaskLater(getPlugin(), () -> { + user.sendMessage("general.request-cancelled"); + toBeConfirmed.remove(user); + }, getPlugin().getSettings().getConfirmationTime() * 20L); + + // Add to the global confirmation map + toBeConfirmed.put(user, new Confirmer(getTopLabel(), getLabel(), confirmed, task)); + } + + private class Confirmer { + private final String topLabel; + private final String label; + private final Runnable runnable; + private final BukkitTask task; + + /** + * @param label - command label + * @param runnable - runnable to run when confirmed + * @param task - task ID to cancel when confirmed + */ + Confirmer(String topLabel, String label, Runnable runnable, BukkitTask task) { + this.topLabel = topLabel; + this.label = label; + this.runnable = runnable; + this.task = task; + } + /** + * @return the topLabel + */ + public String getTopLabel() { + return topLabel; + } + /** + * @return the label + */ + public String getLabel() { + return label; + } + /** + * @return the runnable + */ + public Runnable getRunnable() { + return runnable; + } + /** + * @return the task + */ + public BukkitTask getTask() { + return task; + } + + } } diff --git a/src/main/java/us/tastybento/bskyblock/api/flags/Flag.java b/src/main/java/us/tastybento/bskyblock/api/flags/Flag.java index 8f5990127..b88e486de 100644 --- a/src/main/java/us/tastybento/bskyblock/api/flags/Flag.java +++ b/src/main/java/us/tastybento/bskyblock/api/flags/Flag.java @@ -14,8 +14,6 @@ import us.tastybento.bskyblock.api.user.User; import us.tastybento.bskyblock.database.objects.Island; import us.tastybento.bskyblock.managers.RanksManager; -import javax.xml.soap.Text; - public class Flag implements Comparable { public enum Type { diff --git a/src/main/java/us/tastybento/bskyblock/commands/admin/AdminSchemCommand.java b/src/main/java/us/tastybento/bskyblock/commands/admin/AdminSchemCommand.java index 38f06f76e..74be92a46 100644 --- a/src/main/java/us/tastybento/bskyblock/commands/admin/AdminSchemCommand.java +++ b/src/main/java/us/tastybento/bskyblock/commands/admin/AdminSchemCommand.java @@ -51,13 +51,16 @@ public class AdminSchemCommand extends CompositeCommand { if (args.get(0).equalsIgnoreCase("load")) { if (args.size() == 2) { - File file = new File(schemFolder, args.get(1) + ".schem"); + File file = new File(schemFolder, args.get(1)); if (file.exists()) { try { cb.load(file); + user.sendMessage("general.success"); + clipboards.put(user.getUniqueId(), cb); return true; } catch (Exception e) { user.sendMessage("commands.admin.schem.could-not-load"); + e.printStackTrace(); return false; } } else { diff --git a/src/main/java/us/tastybento/bskyblock/commands/island/IslandResetCommand.java b/src/main/java/us/tastybento/bskyblock/commands/island/IslandResetCommand.java index c3667f4c0..485cdc942 100644 --- a/src/main/java/us/tastybento/bskyblock/commands/island/IslandResetCommand.java +++ b/src/main/java/us/tastybento/bskyblock/commands/island/IslandResetCommand.java @@ -6,7 +6,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import org.bukkit.Bukkit; import org.bukkit.GameMode; import org.bukkit.entity.Player; @@ -63,38 +62,14 @@ public class IslandResetCommand extends CompositeCommand { user.sendMessage("commands.island.reset.resets-left", TextVariables.NUMBER, String.valueOf(getPlayers().getResetsLeft(user.getUniqueId()))); } } - // Check for non-confirm command - if (!args.isEmpty() && !(confirm.containsKey(user.getUniqueId()) && args.get(0).equalsIgnoreCase("confirm"))) { - showHelp(this, user); - return false; - } - - // Check confirmation or reset immediately if no confirmation required - if (!getSettings().isResetConfirmation() || (confirm.containsKey(user.getUniqueId()) && args.size() == 1 && args.get(0).equalsIgnoreCase("confirm"))) { + // Request confirmation + if (getSettings().isResetConfirmation()) { + this.askConfirmation(user, () -> resetIsland(user)); + return true; + } else { return resetIsland(user); } - - // Confirmation required - if (!confirm.containsKey(user.getUniqueId())) { - requestConfirmation(user); - } else { - // Show how many seconds left to confirm - int time = (int)((confirm.get(user.getUniqueId()) - System.currentTimeMillis()) / 1000D); - user.sendMessage("commands.island.reset.confirm", TextVariables.LABEL, getTopLabel(), TextVariables.NUMBER, String.valueOf(time)); - } - return true; - } - private void requestConfirmation(User user) { - user.sendMessage("commands.island.reset.confirm", TextVariables.LABEL, getTopLabel(), TextVariables.NUMBER, String.valueOf(getSettings().getConfirmationTime())); - // Require confirmation - confirm.put(user.getUniqueId(), System.currentTimeMillis() + getSettings().getConfirmationTime() * 1000L); - Bukkit.getScheduler().runTaskLater(getPlugin(), () -> { - if (confirm.containsKey(user.getUniqueId())) { - user.sendMessage("commands.island.reset.cancelled"); - confirm.remove(user.getUniqueId()); - } - }, getSettings().getConfirmationTime() * 20L); } private boolean resetIsland(User user) { diff --git a/src/main/java/us/tastybento/bskyblock/commands/island/teams/IslandTeamCommand.java b/src/main/java/us/tastybento/bskyblock/commands/island/teams/IslandTeamCommand.java index 052cb979d..bda91bbd4 100644 --- a/src/main/java/us/tastybento/bskyblock/commands/island/teams/IslandTeamCommand.java +++ b/src/main/java/us/tastybento/bskyblock/commands/island/teams/IslandTeamCommand.java @@ -10,8 +10,6 @@ import us.tastybento.bskyblock.api.events.team.TeamEvent; import us.tastybento.bskyblock.api.localization.TextVariables; import us.tastybento.bskyblock.api.user.User; -import javax.xml.soap.Text; - public class IslandTeamCommand extends CompositeCommand { private IslandTeamInviteCommand inviteCommand; diff --git a/src/main/java/us/tastybento/bskyblock/island/builders/Clipboard.java b/src/main/java/us/tastybento/bskyblock/island/builders/Clipboard.java index b65243131..b071d9e2c 100644 --- a/src/main/java/us/tastybento/bskyblock/island/builders/Clipboard.java +++ b/src/main/java/us/tastybento/bskyblock/island/builders/Clipboard.java @@ -28,6 +28,7 @@ import org.bukkit.block.banner.PatternType; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.material.Attachable; @@ -101,6 +102,8 @@ public class Clipboard { user.sendMessage("commands.admin.schem.need-pos1-pos2"); return false; } + // Clear the clipboard + blockConfig = new YamlConfiguration(); int count = 0; for (int x = Math.min(pos1.getBlockX(), pos2.getBlockX()); x <= Math.max(pos1.getBlockX(),pos2.getBlockX()); x++) { for (int y = Math.min(pos1.getBlockY(), pos2.getBlockY()); y <= Math.max(pos1.getBlockY(),pos2.getBlockY()); y++) { @@ -222,15 +225,12 @@ public class Clipboard { } bs.update(true, false); + if (bs instanceof InventoryHolder) { Bukkit.getLogger().info("Inventory holder " + s.getCurrentPath()); - - InventoryHolder ih = (InventoryHolder)bs; - @SuppressWarnings("unchecked") - List items = (List) s.get("inventory"); - for (int i = 0; i < ih.getInventory().getSize(); i++) { - ih.getInventory().setItem(i, items.get(i)); - } + Inventory ih = ((InventoryHolder)bs).getInventory(); + ConfigurationSection inv = s.getConfigurationSection("inventory"); + inv.getKeys(false).forEach(i -> ih.setItem(Integer.valueOf(i), (ItemStack)inv.get(i))); } } @@ -307,10 +307,16 @@ public class Clipboard { if (bs instanceof InventoryHolder) { Bukkit.getLogger().info("Inventory holder"); InventoryHolder ih = (InventoryHolder)bs; - s.set("inventory", ih.getInventory().getContents()); + for (int index = 0; index < ih.getInventory().getSize(); index++) { + ItemStack i = ih.getInventory().getItem(index); + if (i != null) { + s.set("inventory." + index, i); + } + } } } + /** * @return the blockConfig */ @@ -326,6 +332,7 @@ public class Clipboard { */ public void load(File file) throws IOException, InvalidConfigurationException { unzip(file.getAbsolutePath()); + blockConfig = new YamlConfiguration(); blockConfig.load(file); copied = true; Files.delete(file.toPath()); @@ -375,7 +382,7 @@ public class Clipboard { } private void zip(File targetFile) throws IOException { - try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(targetFile.getAbsolutePath() + ".schem"))) { + try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(targetFile.getAbsolutePath() + ".zip"))) { zipOutputStream.putNextEntry(new ZipEntry(targetFile.getName())); try (FileInputStream inputStream = new FileInputStream(targetFile)) { final byte[] buffer = new byte[1024]; diff --git a/src/main/java/us/tastybento/bskyblock/listeners/protection/InventorySave.java b/src/main/java/us/tastybento/bskyblock/listeners/protection/InventorySave.java index 04b1ad904..46fb173e9 100644 --- a/src/main/java/us/tastybento/bskyblock/listeners/protection/InventorySave.java +++ b/src/main/java/us/tastybento/bskyblock/listeners/protection/InventorySave.java @@ -5,8 +5,6 @@ import java.util.UUID; import org.bukkit.entity.Player; -import us.tastybento.bskyblock.BSkyBlock; - /** * Stashes inventories when required for a player * diff --git a/src/test/java/us/tastybento/bskyblock/commands/island/IslandResetCommandTest.java b/src/test/java/us/tastybento/bskyblock/commands/island/IslandResetCommandTest.java index d218f4a10..135dd8d9b 100644 --- a/src/test/java/us/tastybento/bskyblock/commands/island/IslandResetCommandTest.java +++ b/src/test/java/us/tastybento/bskyblock/commands/island/IslandResetCommandTest.java @@ -10,13 +10,13 @@ import static org.mockito.Mockito.when; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.scheduler.BukkitTask; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,7 +28,6 @@ import org.powermock.reflect.Whitebox; import us.tastybento.bskyblock.BSkyBlock; import us.tastybento.bskyblock.Settings; -import us.tastybento.bskyblock.api.localization.TextVariables; import us.tastybento.bskyblock.api.user.User; import us.tastybento.bskyblock.commands.IslandCommand; import us.tastybento.bskyblock.database.objects.Island; @@ -100,9 +99,13 @@ public class IslandResetCommandTest { // Server & Scheduler BukkitScheduler sch = mock(BukkitScheduler.class); + BukkitTask task = mock(BukkitTask.class); + when(sch.runTaskLater(Mockito.any(), Mockito.any(Runnable.class), Mockito.any(Long.class))).thenReturn(task); + PowerMockito.mockStatic(Bukkit.class); when(Bukkit.getScheduler()).thenReturn(sch); + // IWM friendly name IslandWorldManager iwm = mock(IslandWorldManager.class); when(iwm.getFriendlyName(Mockito.any())).thenReturn("BSkyBlock"); @@ -161,22 +164,6 @@ public class IslandResetCommandTest { Mockito.verify(user).sendMessage("commands.island.reset.none-left"); } - @Test - public void testConfirmBeforeReset() throws IOException { - IslandResetCommand irc = new IslandResetCommand(ic); - // Now has island, but is not the leader - when(im.hasIsland(Mockito.any(), Mockito.eq(uuid))).thenReturn(true); - // Now is owner, but still has team - when(im.isOwner(Mockito.any(), Mockito.eq(uuid))).thenReturn(true); - // Now has no team - when(im.inTeam(Mockito.any(), Mockito.eq(uuid))).thenReturn(false); - // Give the user some resets - when(pm.getResetsLeft(Mockito.eq(uuid))).thenReturn(1); - - // Test sending confirm immediately - assertFalse(irc.execute(user, Arrays.asList("confirm"))); - } - @Test public void testNoConfirmationRequired() throws IOException { IslandResetCommand irc = new IslandResetCommand(ic); @@ -278,15 +265,15 @@ public class IslandResetCommandTest { // Require confirmation when(s.isResetConfirmation()).thenReturn(true); when(s.getConfirmationTime()).thenReturn(20); - + // Reset assertTrue(irc.execute(user, new ArrayList<>())); - Mockito.verify(user).sendMessage("commands.island.reset.confirm", TextVariables.LABEL, "island", TextVariables.NUMBER, String.valueOf(s.getConfirmationTime())); + // Check for message + Mockito.verify(user).sendMessage("general.confirm", "[seconds]", String.valueOf(s.getConfirmationTime())); + + // Send command again to confirm + assertTrue(irc.execute(user, new ArrayList<>())); - // Reset confirm - assertTrue(irc.execute(user, Arrays.asList("confirm"))); - Mockito.verify(builder).build(); - } @Test