Merge branch 'confirmation' into develop

Conflicts:
	locales/en-US.yml
	src/main/java/us/tastybento/bskyblock/commands/island/IslandResetCommand.java
This commit is contained in:
tastybento 2018-06-03 14:03:46 -07:00
commit 1289060bd4
9 changed files with 144 additions and 89 deletions

View File

@ -17,6 +17,9 @@ banner:
general: general:
success: "&aSuccess!" 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: errors:
command-cancelled: "&cCommand cancelled" command-cancelled: "&cCommand cancelled"
no-permission: "&cYou don't have permission to execute this command." 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" description: "set a player's rank on their island"
unknown-rank: "&cUnknown rank!" unknown-rank: "&cUnknown rank!"
rank-set: "&aRank set from [from] to [to]." rank-set: "&aRank set from [from] to [to]."
schem:
parameters: "<load><copy><paste><pos1><pos2><save>"
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: island:
about: about:
description: "display copyright and license info" 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 <player>)." must-remove-members: "You must remove all members from your island before you can restart it (/island kick <player>)."
none-left: "&cYou have no more resets left!" none-left: "&cYou have no more resets left!"
resets-left: "&cYou have [number] 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: sethome:
description: "set your teleport point for /island" description: "set your teleport point for /island"
must-be-on-your-island: "You must be on your island to set home!" must-be-on-your-island: "You must be on your island to set home!"

View File

@ -2,6 +2,7 @@ package us.tastybento.bskyblock.api.commands;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -11,11 +12,13 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginIdentifiableCommand; import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitTask;
import us.tastybento.bskyblock.BSkyBlock; import us.tastybento.bskyblock.BSkyBlock;
import us.tastybento.bskyblock.Settings; import us.tastybento.bskyblock.Settings;
@ -35,7 +38,7 @@ import us.tastybento.bskyblock.util.Util;
*/ */
public abstract class CompositeCommand extends Command implements PluginIdentifiableCommand, BSBCommand { 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) * True if the command is for the player only (not for the console)
@ -92,6 +95,8 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
*/ */
private String topLabel = ""; private String topLabel = "";
private static Map<User, Confirmer> toBeConfirmed = new HashMap<>();
/** /**
* Used only for testing.... * Used only for testing....
*/ */
@ -225,14 +230,13 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
if (event.isCancelled()) { if (event.isCancelled()) {
return true; return true;
} }
// Execute and trim args // Execute and trim args
return cmd.execute(user, Arrays.asList(args).subList(cmd.subCommandLevel, args.length)); return cmd.execute(user, Arrays.asList(args).subList(cmd.subCommandLevel, args.length));
} }
/** /**
* Get the current composite command based on the arguments * Get the current composite command based on the arguments
* @param args * @param args - arguments
* @return the current composite command based on the arguments * @return the current composite command based on the arguments
*/ */
private CompositeCommand getCommandFromArgs(String[] args) { private CompositeCommand getCommandFromArgs(String[] args) {
@ -240,7 +244,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
// Run through any arguments // Run through any arguments
for (String arg : args) { for (String arg : args) {
// get the subcommand corresponding to the arg // get the subcommand corresponding to the arg
if (subCommand.hasSubCommmands()) { if (subCommand.hasSubCommands()) {
Optional<CompositeCommand> sub = subCommand.getSubCommand(arg); Optional<CompositeCommand> sub = subCommand.getSubCommand(arg);
if (!sub.isPresent()) { if (!sub.isPresent()) {
return subCommand; return subCommand;
@ -375,7 +379,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/** /**
* Check if this command has a specific sub command * Check if this command has a specific sub command
* @param subCommand * @param subCommand - sub command
* @return true if this command has this sub command * @return true if this command has this sub command
*/ */
protected boolean hasSubCommand(String subCommand) { 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 * Check if this command has any sub commands
* @return true if this command has subcommands * @return true if this command has subcommands
*/ */
protected boolean hasSubCommmands() { protected boolean hasSubCommands() {
return !subCommands.isEmpty(); return !subCommands.isEmpty();
} }
@ -419,7 +423,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/** /**
* Set whether this command is only for players * Set whether this command is only for players
* @param onlyPlayer * @param onlyPlayer - true if command only for players
*/ */
public void setOnlyPlayer(boolean onlyPlayer) { public void setOnlyPlayer(boolean onlyPlayer) {
this.onlyPlayer = 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 * Sets the command parameters to be shown in help
* @param parameters * @param parameters - string of parameters
*/ */
public void setParameters(String parameters) { public void setParameters(String parameters) {
this.parameters = parameters; this.parameters = parameters;
@ -480,7 +484,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
// Add any tab completion from the subcommand // Add any tab completion from the subcommand
options.addAll(cmd.tabComplete(User.getInstance(sender), alias, new LinkedList<>(Arrays.asList(args))).orElse(new ArrayList<>())); options.addAll(cmd.tabComplete(User.getInstance(sender), alias, new LinkedList<>(Arrays.asList(args))).orElse(new ArrayList<>()));
// Add any sub-commands automatically // Add any sub-commands automatically
if (cmd.hasSubCommmands()) { if (cmd.hasSubCommands()) {
// Check if subcommands are visible to this sender // Check if subcommands are visible to this sender
for (CompositeCommand subCommand: cmd.getSubCommands().values()) { for (CompositeCommand subCommand: cmd.getSubCommands().values()) {
if (sender instanceof Player) { if (sender instanceof Player) {
@ -506,7 +510,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
/** /**
* Show help * Show help
* @param command * @param command - command that this help is for
* @param user - the User * @param user - the User
* @return result of help command or false if no help defined * @return result of help command or false if no help defined
*/ */
@ -569,4 +573,78 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi
public String getTopLabel() { public String getTopLabel() {
return topLabel; 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;
}
}
} }

View File

@ -14,8 +14,6 @@ import us.tastybento.bskyblock.api.user.User;
import us.tastybento.bskyblock.database.objects.Island; import us.tastybento.bskyblock.database.objects.Island;
import us.tastybento.bskyblock.managers.RanksManager; import us.tastybento.bskyblock.managers.RanksManager;
import javax.xml.soap.Text;
public class Flag implements Comparable<Flag> { public class Flag implements Comparable<Flag> {
public enum Type { public enum Type {

View File

@ -51,13 +51,16 @@ public class AdminSchemCommand extends CompositeCommand {
if (args.get(0).equalsIgnoreCase("load")) { if (args.get(0).equalsIgnoreCase("load")) {
if (args.size() == 2) { if (args.size() == 2) {
File file = new File(schemFolder, args.get(1) + ".schem"); File file = new File(schemFolder, args.get(1));
if (file.exists()) { if (file.exists()) {
try { try {
cb.load(file); cb.load(file);
user.sendMessage("general.success");
clipboards.put(user.getUniqueId(), cb);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
user.sendMessage("commands.admin.schem.could-not-load"); user.sendMessage("commands.admin.schem.could-not-load");
e.printStackTrace();
return false; return false;
} }
} else { } else {

View File

@ -6,7 +6,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.GameMode; import org.bukkit.GameMode;
import org.bukkit.entity.Player; 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()))); user.sendMessage("commands.island.reset.resets-left", TextVariables.NUMBER, String.valueOf(getPlayers().getResetsLeft(user.getUniqueId())));
} }
} }
// Check for non-confirm command // Request confirmation
if (!args.isEmpty() && !(confirm.containsKey(user.getUniqueId()) && args.get(0).equalsIgnoreCase("confirm"))) { if (getSettings().isResetConfirmation()) {
showHelp(this, user); this.askConfirmation(user, () -> resetIsland(user));
return false; return true;
} } else {
// Check confirmation or reset immediately if no confirmation required
if (!getSettings().isResetConfirmation() || (confirm.containsKey(user.getUniqueId()) && args.size() == 1 && args.get(0).equalsIgnoreCase("confirm"))) {
return resetIsland(user); 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) { private boolean resetIsland(User user) {

View File

@ -10,8 +10,6 @@ import us.tastybento.bskyblock.api.events.team.TeamEvent;
import us.tastybento.bskyblock.api.localization.TextVariables; import us.tastybento.bskyblock.api.localization.TextVariables;
import us.tastybento.bskyblock.api.user.User; import us.tastybento.bskyblock.api.user.User;
import javax.xml.soap.Text;
public class IslandTeamCommand extends CompositeCommand { public class IslandTeamCommand extends CompositeCommand {
private IslandTeamInviteCommand inviteCommand; private IslandTeamInviteCommand inviteCommand;

View File

@ -28,6 +28,7 @@ import org.bukkit.block.banner.PatternType;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException; import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.material.Attachable; import org.bukkit.material.Attachable;
@ -101,6 +102,8 @@ public class Clipboard {
user.sendMessage("commands.admin.schem.need-pos1-pos2"); user.sendMessage("commands.admin.schem.need-pos1-pos2");
return false; return false;
} }
// Clear the clipboard
blockConfig = new YamlConfiguration();
int count = 0; int count = 0;
for (int x = Math.min(pos1.getBlockX(), pos2.getBlockX()); x <= Math.max(pos1.getBlockX(),pos2.getBlockX()); x++) { 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++) { 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); bs.update(true, false);
if (bs instanceof InventoryHolder) { if (bs instanceof InventoryHolder) {
Bukkit.getLogger().info("Inventory holder " + s.getCurrentPath()); Bukkit.getLogger().info("Inventory holder " + s.getCurrentPath());
Inventory ih = ((InventoryHolder)bs).getInventory();
InventoryHolder ih = (InventoryHolder)bs; ConfigurationSection inv = s.getConfigurationSection("inventory");
@SuppressWarnings("unchecked") inv.getKeys(false).forEach(i -> ih.setItem(Integer.valueOf(i), (ItemStack)inv.get(i)));
List<ItemStack> items = (List<ItemStack>) s.get("inventory");
for (int i = 0; i < ih.getInventory().getSize(); i++) {
ih.getInventory().setItem(i, items.get(i));
}
} }
} }
@ -307,10 +307,16 @@ public class Clipboard {
if (bs instanceof InventoryHolder) { if (bs instanceof InventoryHolder) {
Bukkit.getLogger().info("Inventory holder"); Bukkit.getLogger().info("Inventory holder");
InventoryHolder ih = (InventoryHolder)bs; 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 * @return the blockConfig
*/ */
@ -326,6 +332,7 @@ public class Clipboard {
*/ */
public void load(File file) throws IOException, InvalidConfigurationException { public void load(File file) throws IOException, InvalidConfigurationException {
unzip(file.getAbsolutePath()); unzip(file.getAbsolutePath());
blockConfig = new YamlConfiguration();
blockConfig.load(file); blockConfig.load(file);
copied = true; copied = true;
Files.delete(file.toPath()); Files.delete(file.toPath());
@ -375,7 +382,7 @@ public class Clipboard {
} }
private void zip(File targetFile) throws IOException { 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())); zipOutputStream.putNextEntry(new ZipEntry(targetFile.getName()));
try (FileInputStream inputStream = new FileInputStream(targetFile)) { try (FileInputStream inputStream = new FileInputStream(targetFile)) {
final byte[] buffer = new byte[1024]; final byte[] buffer = new byte[1024];

View File

@ -5,8 +5,6 @@ import java.util.UUID;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import us.tastybento.bskyblock.BSkyBlock;
/** /**
* Stashes inventories when required for a player * Stashes inventories when required for a player
* *

View File

@ -10,13 +10,13 @@ import static org.mockito.Mockito.when;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.UUID; import java.util.UUID;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -28,7 +28,6 @@ import org.powermock.reflect.Whitebox;
import us.tastybento.bskyblock.BSkyBlock; import us.tastybento.bskyblock.BSkyBlock;
import us.tastybento.bskyblock.Settings; import us.tastybento.bskyblock.Settings;
import us.tastybento.bskyblock.api.localization.TextVariables;
import us.tastybento.bskyblock.api.user.User; import us.tastybento.bskyblock.api.user.User;
import us.tastybento.bskyblock.commands.IslandCommand; import us.tastybento.bskyblock.commands.IslandCommand;
import us.tastybento.bskyblock.database.objects.Island; import us.tastybento.bskyblock.database.objects.Island;
@ -100,9 +99,13 @@ public class IslandResetCommandTest {
// Server & Scheduler // Server & Scheduler
BukkitScheduler sch = mock(BukkitScheduler.class); 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); PowerMockito.mockStatic(Bukkit.class);
when(Bukkit.getScheduler()).thenReturn(sch); when(Bukkit.getScheduler()).thenReturn(sch);
// IWM friendly name // IWM friendly name
IslandWorldManager iwm = mock(IslandWorldManager.class); IslandWorldManager iwm = mock(IslandWorldManager.class);
when(iwm.getFriendlyName(Mockito.any())).thenReturn("BSkyBlock"); when(iwm.getFriendlyName(Mockito.any())).thenReturn("BSkyBlock");
@ -161,22 +164,6 @@ public class IslandResetCommandTest {
Mockito.verify(user).sendMessage("commands.island.reset.none-left"); 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 @Test
public void testNoConfirmationRequired() throws IOException { public void testNoConfirmationRequired() throws IOException {
IslandResetCommand irc = new IslandResetCommand(ic); IslandResetCommand irc = new IslandResetCommand(ic);
@ -281,11 +268,11 @@ public class IslandResetCommandTest {
// Reset // Reset
assertTrue(irc.execute(user, new ArrayList<>())); 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()));
// Reset confirm // Send command again to confirm
assertTrue(irc.execute(user, Arrays.asList("confirm"))); assertTrue(irc.execute(user, new ArrayList<>()));
Mockito.verify(builder).build();
} }